home *** CD-ROM | disk | FTP | other *** search
/ Micromanía 92 / CDMM92_1.ISO / SOF 2 SDK / sof2sdk-101.msi / _92D6AC311BB48EBA344BBABC89DA6AB0 / _194CD16071A8496387FD37548E45FBD4 < prev    next >
Encoding:
Text File  |  2002-07-01  |  116.1 KB  |  5,506 lines

  1. // Copyright (C) 2001-2002 Raven Software.
  2. //
  3.  
  4. /*****************************************************************************
  5.  * name:        ai_main.c
  6.  *
  7.  * desc:        Quake3 bot AI
  8.  *
  9.  * $Archive: /MissionPack/code/game/ai_main.c $
  10.  * $Author: Mrelusive $ 
  11.  * $Revision: 35 $
  12.  * $Modtime: 6/06/01 1:11p $
  13.  * $Date: 6/06/01 12:06p $
  14.  *
  15.  *****************************************************************************/
  16.  
  17.  
  18. #include "g_local.h"
  19. #include "q_shared.h"
  20. #include "botlib.h"        //bot lib interface
  21. #include "be_aas.h"
  22. #include "be_ea.h"
  23. #include "be_ai_char.h"
  24. #include "be_ai_chat.h"
  25. #include "be_ai_gen.h"
  26. #include "be_ai_goal.h"
  27. #include "be_ai_move.h"
  28. #include "be_ai_weap.h"
  29. //
  30. #include "ai_main.h"
  31. //
  32. #include "chars.h"
  33. #include "inv.h"
  34. #include "syn.h"
  35.  
  36. /*
  37. #define BOT_CTF_DEBUG    1
  38. */
  39.  
  40. #define MAX_PATH        144
  41.  
  42. #define BOT_THINK_TIME    0
  43.  
  44. //bot states
  45. bot_state_t    *botstates[MAX_CLIENTS];
  46. //number of bots
  47. int numbots;
  48. //floating point time
  49. float floattime;
  50. //time to do a regular update
  51. float regularupdate_time;
  52. //
  53.  
  54. boteventtracker_t gBotEventTracker[MAX_CLIENTS];
  55.  
  56. //rww - new bot cvars..
  57. #ifdef _DEBUG
  58. vmCvar_t bot_debugmessages;
  59. #endif
  60.  
  61. vmCvar_t bot_attachments;
  62. vmCvar_t bot_camp;
  63. vmCvar_t bot_pause;
  64.  
  65. vmCvar_t bot_wp_info;
  66. vmCvar_t bot_wp_edit;
  67. vmCvar_t bot_wp_clearweight;
  68. vmCvar_t bot_wp_distconnect;
  69. vmCvar_t bot_wp_visconnect;
  70. //end rww
  71.  
  72. wpobject_t *flagRed;
  73. wpobject_t *oFlagRed;
  74. wpobject_t *flagBlue;
  75. wpobject_t *oFlagBlue;
  76.  
  77. gentity_t *eFlagRed;
  78. gentity_t *droppedRedFlag;
  79. gentity_t *eFlagBlue;
  80. gentity_t *droppedBlueFlag;
  81.  
  82. char *ctfStateNames[] = {
  83.     "CTFSTATE_NONE",
  84.     "CTFSTATE_ATTACKER",
  85.     "CTFSTATE_DEFENDER",
  86.     "CTFSTATE_RETRIEVAL",
  87.     "CTFSTATE_GUARDCARRIER",
  88.     "CTFSTATE_GETFLAGHOME",
  89.     "CTFSTATE_MAXCTFSTATES"
  90. };
  91.  
  92. char *ctfStateDescriptions[] = {
  93.     "I'm not occupied",
  94.     "I'm attacking the enemy's base",
  95.     "I'm defending our base",
  96.     "I'm getting our flag back",
  97.     "I'm escorting our flag carrier",
  98.     "I've got the enemy's flag"
  99. };
  100.  
  101. char *teamplayStateDescriptions[] = {
  102.     "I'm not occupied",
  103.     "I'm following my squad commander",
  104.     "I'm assisting my commanding",
  105.     "I'm attempting to regroup and form a new squad"
  106. };
  107.  
  108. void BotStraightTPOrderCheck(gentity_t *ent, int ordernum, bot_state_t *bs)
  109. {
  110.     switch (ordernum)
  111.     {
  112.     case 0:
  113.         if (bs->squadLeader == ent)
  114.         {
  115.             bs->teamplayState = 0;
  116.             bs->squadLeader = NULL;
  117.         }
  118.         break;
  119.     case TEAMPLAYSTATE_FOLLOWING:
  120.         bs->teamplayState = ordernum;
  121.         bs->isSquadLeader = 0;
  122.         bs->squadLeader = ent;
  123.         bs->wpDestSwitchTime = 0;
  124.         break;
  125.     case TEAMPLAYSTATE_ASSISTING:
  126.         bs->teamplayState = ordernum;
  127.         bs->isSquadLeader = 0;
  128.         bs->squadLeader = ent;
  129.         bs->wpDestSwitchTime = 0;
  130.         break;
  131.     default:
  132.         bs->teamplayState = ordernum;
  133.         break;
  134.     }
  135. }
  136.  
  137. void BotReportStatus(bot_state_t *bs)
  138. {
  139.     if ( level.gametypeData->teams )
  140.     {
  141.         trap_EA_SayTeam(bs->client, teamplayStateDescriptions[bs->teamplayState]);
  142.     }
  143. }
  144.  
  145. void BotOrder(gentity_t *ent, int clientnum, int ordernum)
  146. {
  147.     int stateMin = 0;
  148.     int stateMax = 0;
  149.     int i = 0;
  150.  
  151.     if (!ent || !ent->client)
  152.     {
  153.         return;
  154.     }
  155.  
  156.     if (clientnum != -1 && !botstates[clientnum])
  157.     {
  158.         return;
  159.     }
  160.  
  161.     if (clientnum != -1 && !OnSameTeam(ent, &g_entities[clientnum]))
  162.     {
  163.         return;
  164.     }
  165.  
  166.     if ( !level.gametypeData->teams )
  167.     {
  168.         return;
  169.     }
  170.  
  171. /*
  172.     if (level.gametype == GT_CTF)
  173.     {
  174.         stateMin = CTFSTATE_NONE;
  175.         stateMax = CTFSTATE_MAXCTFSTATES;
  176.     }
  177.     else if (level.gametype == GT_TDM)
  178.     {
  179.         stateMin = TEAMPLAYSTATE_NONE;
  180.         stateMax = TEAMPLAYSTATE_MAXTPSTATES;
  181.     }
  182. */
  183.  
  184.     if ((ordernum < stateMin && ordernum != -1) || ordernum >= stateMax)
  185.     {
  186.         return;
  187.     }
  188.  
  189.     if (clientnum != -1)
  190.     {
  191.         if (ordernum == -1)
  192.         {
  193.             BotReportStatus(botstates[clientnum]);
  194.         }
  195.         else
  196.         {
  197.             BotStraightTPOrderCheck(ent, ordernum, botstates[clientnum]);
  198.             botstates[clientnum]->state_Forced = ordernum;
  199.             botstates[clientnum]->chatObject = ent;
  200.             botstates[clientnum]->chatAltObject = NULL;
  201.             if (BotDoChat(botstates[clientnum], "OrderAccepted", 1))
  202.             {
  203.                 botstates[clientnum]->chatTeam = 1;
  204.             }
  205.         }
  206.     }
  207.     else
  208.     {
  209.         while (i < MAX_CLIENTS)
  210.         {
  211.             if (botstates[i] && OnSameTeam(ent, &g_entities[i]))
  212.             {
  213.                 if (ordernum == -1)
  214.                 {
  215.                     BotReportStatus(botstates[i]);
  216.                 }
  217.                 else
  218.                 {
  219.                     BotStraightTPOrderCheck(ent, ordernum, botstates[i]);
  220.                     botstates[i]->state_Forced = ordernum;
  221.                     botstates[i]->chatObject = ent;
  222.                     botstates[i]->chatAltObject = NULL;
  223.                     if (BotDoChat(botstates[i], "OrderAccepted", 0))
  224.                     {
  225.                         botstates[i]->chatTeam = 1;
  226.                     }
  227.                 }
  228.             }
  229.  
  230.             i++;
  231.         }
  232.     }
  233. }
  234.  
  235. int BotGetWeaponRange(bot_state_t *bs);
  236. int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent);
  237.  
  238. void ExitLevel( void );
  239.  
  240. void QDECL BotAI_Print(int type, char *fmt, ...) { return; }
  241.  
  242. int IsTeamplay(void)
  243. {
  244.     return level.gametypeData->teams;
  245. }
  246.  
  247. /*
  248. ==================
  249. BotAI_GetClientState
  250. ==================
  251. */
  252. int BotAI_GetClientState( int clientNum, playerState_t *state ) {
  253.     gentity_t    *ent;
  254.  
  255.     ent = &g_entities[clientNum];
  256.     if ( !ent->inuse ) {
  257.         return qfalse;
  258.     }
  259.     if ( !ent->client ) {
  260.         return qfalse;
  261.     }
  262.  
  263.     memcpy( state, &ent->client->ps, sizeof(playerState_t) );
  264.     return qtrue;
  265. }
  266.  
  267. /*
  268. ==================
  269. BotAI_GetEntityState
  270. ==================
  271. */
  272. int BotAI_GetEntityState( int entityNum, entityState_t *state ) {
  273.     gentity_t    *ent;
  274.  
  275.     ent = &g_entities[entityNum];
  276.     memset( state, 0, sizeof(entityState_t) );
  277.     if (!ent->inuse) return qfalse;
  278.     if (!ent->r.linked) return qfalse;
  279.     if (ent->r.svFlags & SVF_NOCLIENT) return qfalse;
  280.     memcpy( state, &ent->s, sizeof(entityState_t) );
  281.     return qtrue;
  282. }
  283.  
  284. /*
  285. ==================
  286. BotAI_GetSnapshotEntity
  287. ==================
  288. */
  289. int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) {
  290.     int        entNum;
  291.  
  292.     entNum = trap_BotGetSnapshotEntity( clientNum, sequence );
  293.     if ( entNum == -1 ) {
  294.         memset(state, 0, sizeof(entityState_t));
  295.         return -1;
  296.     }
  297.  
  298.     BotAI_GetEntityState( entNum, state );
  299.  
  300.     return sequence + 1;
  301. }
  302.  
  303. /*
  304. ==============
  305. BotEntityInfo
  306. ==============
  307. */
  308. void BotEntityInfo(int entnum, aas_entityinfo_t *info) {
  309.     trap_AAS_EntityInfo(entnum, info);
  310. }
  311.  
  312. /*
  313. ==============
  314. NumBots
  315. ==============
  316. */
  317. int NumBots(void) {
  318.     return numbots;
  319. }
  320.  
  321. /*
  322. ==============
  323. AngleDifference
  324. ==============
  325. */
  326. float AngleDifference(float ang1, float ang2) {
  327.     float diff;
  328.  
  329.     diff = ang1 - ang2;
  330.     if (ang1 > ang2) {
  331.         if (diff > 180.0) diff -= 360.0;
  332.     }
  333.     else {
  334.         if (diff < -180.0) diff += 360.0;
  335.     }
  336.     return diff;
  337. }
  338.  
  339. /*
  340. ==============
  341. BotChangeViewAngle
  342. ==============
  343. */
  344. float BotChangeViewAngle(float angle, float ideal_angle, float speed) {
  345.     float move;
  346.  
  347.     angle = AngleMod(angle);
  348.     ideal_angle = AngleMod(ideal_angle);
  349.     if (angle == ideal_angle) return angle;
  350.     move = ideal_angle - angle;
  351.     if (ideal_angle > angle) {
  352.         if (move > 180.0) move -= 360.0;
  353.     }
  354.     else {
  355.         if (move < -180.0) move += 360.0;
  356.     }
  357.     if (move > 0) {
  358.         if (move > speed) move = speed;
  359.     }
  360.     else {
  361.         if (move < -speed) move = -speed;
  362.     }
  363.     return AngleMod(angle + move);
  364. }
  365.  
  366. /*
  367. ==============
  368. BotChangeViewAngles
  369. ==============
  370. */
  371. void BotChangeViewAngles(bot_state_t *bs, float thinktime) {
  372.     float diff, factor, maxchange, anglespeed, disired_speed;
  373.     int i;
  374.  
  375.     if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
  376.     
  377.     if (bs->currentEnemy && bs->frame_Enemy_Vis)
  378.     {
  379.         factor = bs->skills.turnspeed_combat*bs->settings.skill;
  380.     }
  381.     else
  382.     {
  383.         factor = bs->skills.turnspeed;
  384.     }
  385.  
  386.     if (factor > 1)
  387.     {
  388.         factor = 1;
  389.     }
  390.     if (factor < 0.001)
  391.     {
  392.         factor = 0.001f;
  393.     }
  394.  
  395.     maxchange = bs->skills.maxturn;
  396.  
  397.     //if (maxchange < 240) maxchange = 240;
  398.     maxchange *= thinktime;
  399.     for (i = 0; i < 2; i++) {
  400.         bs->viewangles[i] = AngleMod(bs->viewangles[i]);
  401.         bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]);
  402.         diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]);
  403.         disired_speed = diff * factor;
  404.         bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed);
  405.         if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange;
  406.         if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange;
  407.         anglespeed = bs->viewanglespeed[i];
  408.         if (anglespeed > maxchange) anglespeed = maxchange;
  409.         if (anglespeed < -maxchange) anglespeed = -maxchange;
  410.         bs->viewangles[i] += anglespeed;
  411.         bs->viewangles[i] = AngleMod(bs->viewangles[i]);
  412.         bs->viewanglespeed[i] *= 0.45 * (1 - factor);
  413.     }
  414.     if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360;
  415.     trap_EA_View(bs->client, bs->viewangles);
  416. }
  417.  
  418. /*
  419. ==============
  420. BotInputToUserCommand
  421. ==============
  422. */
  423. void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time, int useTime) {
  424.     vec3_t angles, forward, right;
  425.     short temp;
  426.     int j;
  427.  
  428.     //clear the whole structure
  429.     memset(ucmd, 0, sizeof(usercmd_t));
  430.     //
  431.     //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
  432.     //the duration for the user command in milli seconds
  433.     ucmd->serverTime = time;
  434.     //
  435.     if (bi->actionflags & ACTION_DELAYEDJUMP) {
  436.         bi->actionflags |= ACTION_JUMP;
  437.         bi->actionflags &= ~ACTION_DELAYEDJUMP;
  438.     }
  439.     //set the buttons
  440.     if (bi->actionflags & ACTION_RESPAWN) 
  441.     {
  442.         ucmd->buttons = BUTTON_ATTACK;
  443.     }
  444.     if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK;
  445.     if (bi->actionflags & ACTION_ALT_ATTACK) ucmd->buttons |= BUTTON_ALT_ATTACK;
  446. //    if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
  447. //    if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
  448. #ifdef BOT_USE_HOLDABLE
  449.     if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE;
  450. #endif
  451.     if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING;
  452.  
  453.     if (useTime < level.time && Q_irand(1, 10) < 5)
  454.     { //for now just hit use randomly in case there's something useable around
  455.         ucmd->buttons |= BUTTON_USE;
  456.     }
  457. #if 0
  458. // Here's an interesting bit.  The bots in TA used buttons to do additional gestures.
  459. // I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2.
  460. // We can always add some back in if we want though.
  461.     if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE;
  462.     if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE;
  463.     if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG;
  464.     if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE;
  465.     if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL;
  466.     if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME;
  467. #endif //0
  468.  
  469.     //
  470.     ucmd->weapon = bi->weapon;
  471.     //set the view angles
  472.     //NOTE: the ucmd->angles are the angles WITHOUT the delta angles
  473.     ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]);
  474.     ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]);
  475.     ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]);
  476.     //subtract the delta angles
  477.     for (j = 0; j < 3; j++) {
  478.         temp = ucmd->angles[j] - delta_angles[j];
  479.         ucmd->angles[j] = temp;
  480.     }
  481.     //NOTE: movement is relative to the REAL view angles
  482.     //get the horizontal forward and right vector
  483.     //get the pitch in the range [-180, 180]
  484.     if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH];
  485.     else angles[PITCH] = 0;
  486.     angles[YAW] = bi->viewangles[YAW];
  487.     angles[ROLL] = 0;
  488.     AngleVectors(angles, forward, right, NULL);
  489.     //bot input speed is in the range [0, 400]
  490.     bi->speed = bi->speed * 127 / 400;
  491.     //set the view independent movement
  492.     ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed;
  493.     ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed;
  494.     ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed;
  495.     //normal keyboard movement
  496.     if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127;
  497.     if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127;
  498.     if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127;
  499.     if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127;
  500.     //jump/moveup
  501.     if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127;
  502.     //crouch/movedown
  503.     if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127;
  504.     //
  505.     //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
  506.     //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime);
  507. }
  508.  
  509. /*
  510. ==============
  511. BotUpdateInput
  512. ==============
  513. */
  514. void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) {
  515.     bot_input_t bi;
  516.     int j;
  517.  
  518.     //add the delta angles to the bot's current view angles
  519.     for (j = 0; j < 3; j++) {
  520.         bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
  521.     }
  522.     //change the bot view angles
  523.     BotChangeViewAngles(bs, (float) elapsed_time / 1000);
  524.     //retrieve the bot input
  525.     trap_EA_GetInput(bs->client, (float) time / 1000, &bi);
  526.     //respawn hack
  527.     if (bi.actionflags & ACTION_RESPAWN) 
  528.     {
  529.         // IF already trying to respawn or a ghost then cancel the respawn
  530.         if ((bs->lastucmd.buttons & BUTTON_ATTACK) || (bs->cur_ps.pm_flags&PMF_GHOST)) 
  531.         {
  532.             bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK);
  533.         }
  534.     }
  535.  
  536.     //convert the bot input to a usercmd
  537.     BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time, bs->noUseTime);
  538.     //subtract the delta angles
  539.     for (j = 0; j < 3; j++) {
  540.         bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
  541.     }
  542. }
  543.  
  544. /*
  545. ==============
  546. BotAIRegularUpdate
  547. ==============
  548. */
  549. void BotAIRegularUpdate(void) {
  550.     if (regularupdate_time < FloatTime()) {
  551.         trap_BotUpdateEntityItems();
  552.         regularupdate_time = FloatTime() + 0.3;
  553.     }
  554. }
  555.  
  556. /*
  557. ==============
  558. RemoveColorEscapeSequences
  559. ==============
  560. */
  561. void RemoveColorEscapeSequences( char *text ) {
  562.     int i, l;
  563.  
  564.     l = 0;
  565.     for ( i = 0; text[i]; i++ ) {
  566.         if (Q_IsColorString(&text[i])) {
  567.             i++;
  568.             continue;
  569.         }
  570.         if (text[i] > 0x7E)
  571.             continue;
  572.         text[l++] = text[i];
  573.     }
  574.     text[l] = '\0';
  575. }
  576.  
  577.  
  578. /*
  579. ==============
  580. BotAI
  581. ==============
  582. */
  583. int BotAI(int client, float thinktime) {
  584.     bot_state_t *bs;
  585.     char buf[1024], *args;
  586.     int j;
  587. #ifdef _DEBUG
  588.     int start = 0;
  589.     int end = 0;
  590. #endif
  591.  
  592.     trap_EA_ResetInput(client);
  593.     //
  594.     bs = botstates[client];
  595.     if (!bs || !bs->inuse) {
  596.         BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client);
  597.         return qfalse;
  598.     }
  599.  
  600.     //retrieve the current client state
  601.     BotAI_GetClientState( client, &bs->cur_ps );
  602.  
  603.     //retrieve any waiting server commands
  604.     while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) {
  605.         //have buf point to the command and args to the command arguments
  606.         args = strchr( buf, ' ');
  607.         if (!args) continue;
  608.         *args++ = '\0';
  609.  
  610.         //remove color espace sequences from the arguments
  611.         RemoveColorEscapeSequences( args );
  612.  
  613.         if (!Q_stricmp(buf, "cp "))
  614.             { /*CenterPrintf*/ }
  615.         else if (!Q_stricmp(buf, "cs"))
  616.             { /*ConfigStringModified*/ }
  617.         else if (!Q_stricmp(buf, "scores"))
  618.             { /*FIXME: parse scores?*/ }
  619.         else if (!Q_stricmp(buf, "clientLevelShot"))
  620.             { /*ignore*/ }
  621.     }
  622.     //add the delta angles to the bot's current view angles
  623.     for (j = 0; j < 3; j++) {
  624.         bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
  625.     }
  626.     //increase the local time of the bot
  627.     bs->ltime += thinktime;
  628.     //
  629.     bs->thinktime = thinktime;
  630.     //origin of the bot
  631.     VectorCopy(bs->cur_ps.origin, bs->origin);
  632.     //eye coordinates of the bot
  633.     VectorCopy(bs->cur_ps.origin, bs->eye);
  634.     bs->eye[2] += bs->cur_ps.viewheight;
  635.     //get the area the bot is in
  636.  
  637. #ifdef _DEBUG
  638.     start = trap_Milliseconds();
  639. #endif
  640.     StandardBotAI(bs, thinktime);
  641. #ifdef _DEBUG
  642.     end = trap_Milliseconds();
  643.  
  644.     trap_Cvar_Update(&bot_debugmessages);
  645.  
  646.     if (bot_debugmessages.integer)
  647.     {
  648.         Com_Printf("Single AI frametime: %i\n", (end - start));
  649.     }
  650. #endif
  651.  
  652.     //subtract the delta angles
  653.     for (j = 0; j < 3; j++) {
  654.         bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
  655.     }
  656.     //everything was ok
  657.     return qtrue;
  658. }
  659.  
  660. /*
  661. ==================
  662. BotScheduleBotThink
  663. ==================
  664. */
  665. void BotScheduleBotThink(void) {
  666.     int i, botnum;
  667.  
  668.     botnum = 0;
  669.  
  670.     for( i = 0; i < MAX_CLIENTS; i++ ) {
  671.         if( !botstates[i] || !botstates[i]->inuse ) {
  672.             continue;
  673.         }
  674.         //initialize the bot think residual time
  675.         botstates[i]->botthink_residual = BOT_THINK_TIME * botnum / numbots;
  676.         botnum++;
  677.     }
  678. }
  679.  
  680. int PlayersInGame(void)
  681. {
  682.     int i = 0;
  683.     gentity_t *ent;
  684.     int pl = 0;
  685.  
  686.     while (i < MAX_CLIENTS)
  687.     {
  688.         ent = &g_entities[i];
  689.  
  690.         if (ent && ent->client && ent->client->pers.connected == CON_CONNECTED)
  691.         {
  692.             pl++;
  693.         }
  694.  
  695.         i++;
  696.     }
  697.  
  698.     return pl;
  699. }
  700.  
  701. /*
  702. ==============
  703. BotAISetupClient
  704. ==============
  705. */
  706. int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) {
  707.     bot_state_t *bs;
  708.  
  709.     if (!botstates[client]) botstates[client] = B_Alloc(sizeof(bot_state_t)); //G_Alloc(sizeof(bot_state_t));
  710.                                                                               //rww - G_Alloc bad! B_Alloc good.
  711.  
  712.     memset(botstates[client], 0, sizeof(bot_state_t));
  713.  
  714.     bs = botstates[client];
  715.  
  716.     if (bs && bs->inuse) {
  717.         BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client);
  718.         return qfalse;
  719.     }
  720.  
  721.     memcpy(&bs->settings, settings, sizeof(bot_settings_t));
  722.  
  723.     bs->client = client; //need to know the client number before doing personality stuff
  724.  
  725.     //initialize weapon weight defaults..
  726.     bs->botWeaponWeights[WP_NONE] = 0;
  727.     bs->botWeaponWeights[WP_KNIFE] = 1;
  728.     bs->botWeaponWeights[WP_M1911A1_PISTOL] = 3;
  729.     bs->botWeaponWeights[WP_USSOCOM_PISTOL] = 2;
  730.     bs->botWeaponWeights[WP_M4_ASSAULT_RIFLE] = 10;
  731.     bs->botWeaponWeights[WP_AK74_ASSAULT_RIFLE] = 9;
  732.     bs->botWeaponWeights[WP_M60_MACHINEGUN] = 11;
  733.     bs->botWeaponWeights[WP_MICRO_UZI_SUBMACHINEGUN] = 8;
  734.     bs->botWeaponWeights[WP_M3A1_SUBMACHINEGUN] = 7;
  735.     bs->botWeaponWeights[WP_MSG90A1] = 11;
  736.     bs->botWeaponWeights[WP_USAS_12_SHOTGUN] = 12;
  737.     bs->botWeaponWeights[WP_M590_SHOTGUN] = 13;
  738.     bs->botWeaponWeights[WP_MM1_GRENADE_LAUNCHER] = 8;
  739.     bs->botWeaponWeights[WP_RPG7_LAUNCHER] = 16;
  740.     bs->botWeaponWeights[WP_M84_GRENADE] = 6;
  741.     bs->botWeaponWeights[WP_SMOHG92_GRENADE] = 2;
  742.     bs->botWeaponWeights[WP_ANM14_GRENADE] = 2;
  743.     bs->botWeaponWeights[WP_M15_GRENADE] = 2;
  744.     bs->botWeaponWeights[WP_MP5] = 7;
  745.  
  746.     BotUtilizePersonality(bs);
  747.  
  748.     //allocate a goal state
  749.     bs->gs = trap_BotAllocGoalState(client);
  750.  
  751.     //allocate a weapon state
  752.     bs->ws = trap_BotAllocWeaponState();
  753.  
  754.     bs->inuse = qtrue;
  755.     bs->entitynum = client;
  756.     bs->setupcount = 4;
  757.     bs->entergame_time = FloatTime();
  758.     bs->ms = trap_BotAllocMoveState();
  759.     numbots++;
  760.  
  761.     //NOTE: reschedule the bot thinking
  762.     BotScheduleBotThink();
  763.  
  764.     if (PlayersInGame())
  765.     { //don't talk to yourself
  766.         BotDoChat(bs, "GeneralGreetings", 0);
  767.     }
  768.  
  769.     return qtrue;
  770. }
  771.  
  772. /*
  773. ==============
  774. BotAIShutdownClient
  775. ==============
  776. */
  777. int BotAIShutdownClient(int client, qboolean restart) {
  778.     bot_state_t *bs;
  779.  
  780.     bs = botstates[client];
  781.     if (!bs || !bs->inuse) {
  782.         //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client);
  783.         return qfalse;
  784.     }
  785.  
  786.     trap_BotFreeMoveState(bs->ms);
  787.     //free the goal state`            
  788.     trap_BotFreeGoalState(bs->gs);
  789.     //free the weapon weights
  790.     trap_BotFreeWeaponState(bs->ws);
  791.     //
  792.     //clear the bot state
  793.     memset(bs, 0, sizeof(bot_state_t));
  794.     //set the inuse flag to qfalse
  795.     bs->inuse = qfalse;
  796.     //there's one bot less
  797.     numbots--;
  798.     //everything went ok
  799.     return qtrue;
  800. }
  801.  
  802. /*
  803. ==============
  804. BotResetState
  805.  
  806. called when a bot enters the intermission or observer mode and
  807. when the level is changed
  808. ==============
  809. */
  810. void BotResetState(bot_state_t *bs) {
  811.     int client, entitynum, inuse;
  812.     int movestate, goalstate, weaponstate;
  813.     bot_settings_t settings;
  814.     playerState_t ps;                            //current player state
  815.     float entergame_time;
  816.  
  817.     //save some things that should not be reset here
  818.     memcpy(&settings, &bs->settings, sizeof(bot_settings_t));
  819.     memcpy(&ps, &bs->cur_ps, sizeof(playerState_t));
  820.     inuse = bs->inuse;
  821.     client = bs->client;
  822.     entitynum = bs->entitynum;
  823.     movestate = bs->ms;
  824.     goalstate = bs->gs;
  825.     weaponstate = bs->ws;
  826.     entergame_time = bs->entergame_time;
  827.     //reset the whole state
  828.     memset(bs, 0, sizeof(bot_state_t));
  829.     //copy back some state stuff that should not be reset
  830.     bs->ms = movestate;
  831.     bs->gs = goalstate;
  832.     bs->ws = weaponstate;
  833.     memcpy(&bs->cur_ps, &ps, sizeof(playerState_t));
  834.     memcpy(&bs->settings, &settings, sizeof(bot_settings_t));
  835.     bs->inuse = inuse;
  836.     bs->client = client;
  837.     bs->entitynum = entitynum;
  838.     bs->entergame_time = entergame_time;
  839.     //reset several states
  840.     if (bs->ms) trap_BotResetMoveState(bs->ms);
  841.     if (bs->gs) trap_BotResetGoalState(bs->gs);
  842.     if (bs->ws) trap_BotResetWeaponState(bs->ws);
  843.     if (bs->gs) trap_BotResetAvoidGoals(bs->gs);
  844.     if (bs->ms) trap_BotResetAvoidReach(bs->ms);
  845. }
  846.  
  847. /*
  848. ==============
  849. BotAILoadMap
  850. ==============
  851. */
  852. int BotAILoadMap( int restart ) {
  853.     int            i;
  854.  
  855.     for (i = 0; i < MAX_CLIENTS; i++) {
  856.         if (botstates[i] && botstates[i]->inuse) {
  857.             BotResetState( botstates[i] );
  858.             botstates[i]->setupcount = 4;
  859.         }
  860.     }
  861.  
  862.     return qtrue;
  863. }
  864.  
  865. //rww - bot ai
  866. int OrgVisible(vec3_t org1, vec3_t org2, int ignore)
  867. {
  868.     trace_t tr;
  869.  
  870.     trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID);
  871.  
  872.     if (tr.fraction == 1)
  873.     {
  874.         return 1;
  875.     }
  876.  
  877.     return 0;
  878. }
  879.  
  880. int WPOrgVisible(gentity_t *bot, vec3_t org1, vec3_t org2, int ignore)
  881. {
  882.     trace_t tr;
  883.  
  884.     trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID);
  885.  
  886.     if (tr.fraction == 1)
  887.     {
  888.         return 1;
  889.     }
  890.  
  891.     return 0;
  892. }
  893.  
  894. int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore)
  895. {
  896.     trace_t tr;
  897.  
  898.     trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID);
  899.  
  900.     if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
  901.     {
  902.         return 1;
  903.     }
  904.  
  905.     return 0;
  906. }
  907.  
  908. int CheckForFunc(vec3_t org, int ignore)
  909. {
  910.     gentity_t *fent;
  911.     vec3_t under;
  912.     trace_t tr;
  913.  
  914.     VectorCopy(org, under);
  915.  
  916.     under[2] -= 64;
  917.  
  918.     trap_Trace(&tr, org, NULL, NULL, under, ignore, MASK_SOLID);
  919.  
  920.     if (tr.fraction == 1)
  921.     {
  922.         return 0;
  923.     }
  924.  
  925.     fent = &g_entities[tr.entityNum];
  926.  
  927.     if (!fent)
  928.     {
  929.         return 0;
  930.     }
  931.  
  932.     if (strstr(fent->classname, "func_"))
  933.     {
  934.         return 1; //there's a func brush here
  935.     }
  936.  
  937.     return 0;
  938. }
  939.  
  940. int GetNearestVisibleWP(vec3_t org, int ignore)
  941. {
  942.     int i;
  943.     float bestdist;
  944.     float flLen;
  945.     int bestindex;
  946.     vec3_t a, mins, maxs;
  947.  
  948.     i = 0;
  949.     bestdist = 800;//99999;
  950.                //don't trace over 800 units away to avoid GIANT HORRIBLE SPEED HITS ^_^
  951.     bestindex = -1;
  952.  
  953.     mins[0] = -15;
  954.     mins[1] = -15;
  955.     mins[2] = -1;
  956.     maxs[0] = 15;
  957.     maxs[1] = 15;
  958.     maxs[2] = 1;
  959.  
  960.     while (i < gWPNum)
  961.     {
  962.         if (gWPArray[i] && gWPArray[i]->inuse)
  963.         {
  964.             VectorSubtract(org, gWPArray[i]->origin, a);
  965.             flLen = VectorLength(a);
  966.  
  967.             if (flLen < bestdist && trap_InPVS(org, gWPArray[i]->origin) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore))
  968.             {
  969.                 bestdist = flLen;
  970.                 bestindex = i;
  971.             }
  972.         }
  973.  
  974.         i++;
  975.     }
  976.  
  977.     return bestindex;
  978. }
  979.  
  980. //wpDirection
  981. //0 == FORWARD
  982. //1 == BACKWARD
  983.  
  984. int PassWayCheck(bot_state_t *bs, int windex)
  985. {
  986.     if (!gWPArray[windex] || !gWPArray[windex]->inuse)
  987.     {
  988.         return 0;
  989.     }
  990.  
  991.     if (bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_FWD))
  992.     {
  993.         return 0;
  994.     }
  995.     else if (!bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_BACK))
  996.     {
  997.         return 0;
  998.     }
  999.  
  1000.     return 1;
  1001. }
  1002.  
  1003. float TotalTrailDistance(int start, int end, bot_state_t *bs)
  1004. {
  1005.     int beginat;
  1006.     int endat;
  1007.     float distancetotal;
  1008.     float gdif = 0;
  1009.  
  1010.     distancetotal = 0;
  1011.  
  1012.     if (start > end)
  1013.     {
  1014.         beginat = end;
  1015.         endat = start;
  1016.     }
  1017.     else
  1018.     {
  1019.         beginat = start;
  1020.         endat = end;
  1021.     }
  1022.  
  1023.     while (beginat < endat)
  1024.     {
  1025.         if (beginat >= gWPNum || !gWPArray[beginat] || !gWPArray[beginat]->inuse)
  1026.         {
  1027.             return -1; //error
  1028.         }
  1029.  
  1030.         if ((end > start && gWPArray[beginat]->flags & WPFLAG_ONEWAY_BACK) ||
  1031.             (start > end && gWPArray[beginat]->flags & WPFLAG_ONEWAY_FWD))
  1032.         {
  1033.             return -1;
  1034.         }
  1035.     
  1036.         if (gWPArray[beginat]->forceJumpTo)
  1037.         {
  1038.             if (gWPArray[beginat-1] && gWPArray[beginat-1]->origin[2]+64 < gWPArray[beginat]->origin[2])
  1039.             {
  1040.                 gdif = gWPArray[beginat]->origin[2] - gWPArray[beginat-1]->origin[2];
  1041.             }
  1042.  
  1043.             if (gdif)
  1044.             {
  1045.             //    if (bs && bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[beginat]->forceJumpTo)
  1046.             //    {
  1047.             //        return -1;
  1048.             //    }
  1049.             }
  1050.         }
  1051.         
  1052.     /*    if (bs->wpCurrent && gWPArray[windex]->forceJumpTo &&
  1053.             gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) &&
  1054.             bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo)
  1055.         {
  1056.             return -1;
  1057.         }*/
  1058.  
  1059.         distancetotal += gWPArray[beginat]->disttonext;
  1060.  
  1061.         beginat++;
  1062.     }
  1063.  
  1064.     return distancetotal;
  1065. }
  1066.  
  1067. void CheckForShorterRoutes(bot_state_t *bs, int newwpindex)
  1068. {
  1069.     float bestlen;
  1070.     float checklen;
  1071.     int bestindex;
  1072.     int i;
  1073.  
  1074.     i = 0;
  1075.  
  1076.     if (!bs->wpDestination)
  1077.     {
  1078.         return;
  1079.     }
  1080.  
  1081.     if (newwpindex < bs->wpDestination->index)
  1082.     {
  1083.         bs->wpDirection = 0;
  1084.     }
  1085.     else if (newwpindex > bs->wpDestination->index)
  1086.     {
  1087.         bs->wpDirection = 1;
  1088.     }
  1089.  
  1090.     if (bs->wpSwitchTime > level.time)
  1091.     {
  1092.         return;
  1093.     }
  1094.  
  1095.     if (!gWPArray[newwpindex]->neighbornum)
  1096.     {
  1097.         return;
  1098.     }
  1099.  
  1100.     bestindex = newwpindex;
  1101.     bestlen = TotalTrailDistance(newwpindex, bs->wpDestination->index, bs);
  1102.  
  1103.     while (i < gWPArray[newwpindex]->neighbornum)
  1104.     {
  1105.         checklen = TotalTrailDistance(gWPArray[newwpindex]->neighbors[i].num, bs->wpDestination->index, bs);
  1106.  
  1107.         if (checklen < bestlen-64 || bestlen == -1)
  1108.         {
  1109.             if (!gWPArray[newwpindex]->neighbors[i].forceJumpTo)
  1110.             {
  1111.                 bestlen = checklen;
  1112.                 bestindex = gWPArray[newwpindex]->neighbors[i].num;
  1113.             }
  1114.         }
  1115.  
  1116.         i++;
  1117.     }
  1118.  
  1119.     if (bestindex != newwpindex && bestindex != -1)
  1120.     {
  1121.         bs->wpCurrent = gWPArray[bestindex];
  1122.         bs->wpSwitchTime = level.time + 3000;
  1123.     }
  1124. }
  1125.  
  1126. void WPConstantRoutine(bot_state_t *bs)
  1127. {
  1128.     if (!bs->wpCurrent)
  1129.     {
  1130.         return;
  1131.     }
  1132.  
  1133.     if (bs->wpCurrent->flags & WPFLAG_DUCK)
  1134.     {
  1135.         bs->duckTime = level.time + 100;
  1136.     }
  1137. }
  1138.  
  1139. qboolean BotCTFGuardDuty(bot_state_t *bs)
  1140. {
  1141. /*
  1142.     if (level.gametype != GT_CTF)
  1143.     {
  1144.         return qfalse;
  1145.     }
  1146.  
  1147.     if (bs->ctfState == CTFSTATE_DEFENDER)
  1148.     {
  1149.         return qtrue;
  1150.     }
  1151. */
  1152.     return qfalse;
  1153. }
  1154.  
  1155. void WPTouchRoutine(bot_state_t *bs)
  1156. {
  1157.     int lastNum;
  1158.  
  1159.     if (!bs->wpCurrent)
  1160.     {
  1161.         return;
  1162.     }
  1163.  
  1164.     bs->wpTravelTime = level.time + 10000;
  1165.  
  1166.     if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC)
  1167.     {
  1168.         bs->noUseTime = level.time + 4000;
  1169.     }
  1170.  
  1171. #ifdef FORCEJUMP_INSTANTMETHOD
  1172.     if ((bs->wpCurrent->flags & WPFLAG_JUMP) && bs->wpCurrent->forceJumpTo)
  1173.     { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is
  1174.       //handled elsewhere.
  1175.         bs->jumpTime = level.time + 100;
  1176.     }
  1177. #else
  1178.     if ((bs->wpCurrent->flags & WPFLAG_JUMP) && !bs->wpCurrent->forceJumpTo)
  1179.     { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is
  1180.       //handled elsewhere.
  1181.         bs->jumpTime = level.time + 100;
  1182.     }
  1183. #endif
  1184.  
  1185.     trap_Cvar_Update(&bot_camp);
  1186.  
  1187.     if (bs->isCamper && bot_camp.integer && (BotIsAChickenWuss(bs) || BotCTFGuardDuty(bs) || bs->isCamper == 2) && ((bs->wpCurrent->flags & WPFLAG_SNIPEORCAMP) || (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)) &&
  1188.         bs->cur_ps.weapon != WP_KNIFE)
  1189.     { //if we're a camper and a chicken then camp
  1190.         if (bs->wpDirection)
  1191.         {
  1192.             lastNum = bs->wpCurrent->index+1;
  1193.         }
  1194.         else
  1195.         {
  1196.             lastNum = bs->wpCurrent->index-1;
  1197.         }
  1198.  
  1199.         if (gWPArray[lastNum] && gWPArray[lastNum]->inuse && gWPArray[lastNum]->index && bs->isCamping < level.time)
  1200.         {
  1201.             bs->isCamping = level.time + rand()%15000 + 30000;
  1202.             bs->wpCamping = bs->wpCurrent;
  1203.             bs->wpCampingTo = gWPArray[lastNum];
  1204.  
  1205.             if (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)
  1206.             {
  1207.                 bs->campStanding = qtrue;
  1208.             }
  1209.             else
  1210.             {
  1211.                 bs->campStanding = qfalse;
  1212.             }
  1213.         }
  1214.  
  1215.     }
  1216.     else if ((bs->cur_ps.weapon == WP_KNIFE) &&
  1217.         bs->isCamping > level.time)
  1218.     {
  1219.         bs->isCamping = 0;
  1220.         bs->wpCampingTo = NULL;
  1221.         bs->wpCamping = NULL;
  1222.     }
  1223.  
  1224.     if (bs->wpDestination)
  1225.     {
  1226.         if (bs->wpCurrent->index == bs->wpDestination->index)
  1227.         {
  1228.             bs->wpDestination = NULL;
  1229.  
  1230.             if (bs->runningLikeASissy)
  1231.             { //this obviously means we're scared and running, so we'll want to keep our navigational priorities less delayed
  1232.                 bs->destinationGrabTime = level.time + 500;
  1233.             }
  1234.             else
  1235.             {
  1236.                 bs->destinationGrabTime = level.time + 3500;
  1237.             }
  1238.         }
  1239.         else
  1240.         {
  1241.             CheckForShorterRoutes(bs, bs->wpCurrent->index);
  1242.         }
  1243.     }
  1244. }
  1245.  
  1246. void MoveTowardIdealAngles(bot_state_t *bs)
  1247. {
  1248.     VectorCopy(bs->goalAngles, bs->ideal_viewangles);
  1249. }
  1250.  
  1251. #define BOT_STRAFE_AVOIDANCE
  1252.  
  1253. #ifdef BOT_STRAFE_AVOIDANCE
  1254. #define STRAFEAROUND_RIGHT            1
  1255. #define STRAFEAROUND_LEFT            2
  1256.  
  1257. int BotTrace_Strafe(bot_state_t *bs, vec3_t traceto)
  1258. {
  1259.     vec3_t playerMins = {-15, -15, /*-24*/-8};
  1260.     vec3_t playerMaxs = {15, 15, 32};
  1261.     vec3_t from, to;
  1262.     vec3_t dirAng, dirDif;
  1263.     vec3_t forward, right;
  1264.     trace_t tr;
  1265.  
  1266.     if (bs->cur_ps.groundEntityNum == ENTITYNUM_NONE)
  1267.     { //don't do this in the air, it can be.. dangerous.
  1268.         return 0;
  1269.     }
  1270.  
  1271.     VectorSubtract(traceto, bs->origin, dirAng);
  1272.     VectorNormalize(dirAng);
  1273.     vectoangles(dirAng, dirAng);
  1274.  
  1275.     if (AngleDifference(bs->viewangles[YAW], dirAng[YAW]) > 60 ||
  1276.         AngleDifference(bs->viewangles[YAW], dirAng[YAW]) < -60)
  1277.     { //If we aren't facing the direction we're going here, then we've got enough excuse to be too stupid to strafe around anyway
  1278.         return 0;
  1279.     }
  1280.  
  1281.     VectorCopy(bs->origin, from);
  1282.     VectorCopy(traceto, to);
  1283.  
  1284.     VectorSubtract(to, from, dirDif);
  1285.     VectorNormalize(dirDif);
  1286.     vectoangles(dirDif, dirDif);
  1287.  
  1288.     AngleVectors(dirDif, forward, 0, 0);
  1289.  
  1290.     to[0] = from[0] + forward[0]*32;
  1291.     to[1] = from[1] + forward[1]*32;
  1292.     to[2] = from[2] + forward[2]*32;
  1293.  
  1294.     trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID);
  1295.  
  1296.     if (tr.fraction == 1)
  1297.     {
  1298.         return 0;
  1299.     }
  1300.  
  1301.     AngleVectors(dirAng, 0, right, 0);
  1302.  
  1303.     from[0] += right[0]*32;
  1304.     from[1] += right[1]*32;
  1305.     from[2] += right[2]*16;
  1306.  
  1307.     to[0] += right[0]*32;
  1308.     to[1] += right[1]*32;
  1309.     to[2] += right[2]*32;
  1310.  
  1311.     trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID);
  1312.  
  1313.     if (tr.fraction == 1)
  1314.     {
  1315.         return STRAFEAROUND_RIGHT;
  1316.     }
  1317.  
  1318.     from[0] -= right[0]*64;
  1319.     from[1] -= right[1]*64;
  1320.     from[2] -= right[2]*64;
  1321.  
  1322.     to[0] -= right[0]*64;
  1323.     to[1] -= right[1]*64;
  1324.     to[2] -= right[2]*64;
  1325.  
  1326.     trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID);
  1327.  
  1328.     if (tr.fraction == 1)
  1329.     {
  1330.         return STRAFEAROUND_LEFT;
  1331.     }
  1332.  
  1333.     return 0;
  1334. }
  1335. #endif
  1336.  
  1337. int BotTrace_Jump(bot_state_t *bs, vec3_t traceto)
  1338. {
  1339.     vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod;
  1340.     trace_t tr;
  1341.     int orTr;
  1342.  
  1343.     VectorSubtract(traceto, bs->origin, a);
  1344.     vectoangles(a, a);
  1345.  
  1346.     AngleVectors(a, fwd, NULL, NULL);
  1347.  
  1348.     traceto_mod[0] = bs->origin[0] + fwd[0]*4;
  1349.     traceto_mod[1] = bs->origin[1] + fwd[1]*4;
  1350.     traceto_mod[2] = bs->origin[2] + fwd[2]*4;
  1351.  
  1352.     mins[0] = -15;
  1353.     mins[1] = -15;
  1354.     mins[2] = -15;
  1355.     maxs[0] = 15;
  1356.     maxs[1] = 15;
  1357.     maxs[2] = 32;
  1358.  
  1359.     trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID);
  1360.  
  1361.     if (tr.fraction == 1)
  1362.     {
  1363.         return 0;
  1364.     }
  1365.  
  1366.     orTr = tr.entityNum;
  1367.  
  1368.     VectorCopy(bs->origin, tracefrom_mod);
  1369.  
  1370.     tracefrom_mod[2] += 41;
  1371.     traceto_mod[2] += 41;
  1372.  
  1373.     mins[0] = -15;
  1374.     mins[1] = -15;
  1375.     mins[2] = 0;
  1376.     maxs[0] = 15;
  1377.     maxs[1] = 15;
  1378.     maxs[2] = 8;
  1379.  
  1380.     trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID);
  1381.  
  1382.     if (tr.fraction == 1)
  1383.     {
  1384.         if (orTr >= 0 && orTr < MAX_CLIENTS && botstates[orTr] && botstates[orTr]->jumpTime > level.time)
  1385.         {
  1386.             return 0; //so bots don't try to jump over each other at the same time
  1387.         }
  1388.  
  1389.         if (bs->currentEnemy && bs->currentEnemy->s.number == orTr && (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER || BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE))
  1390.         {
  1391.             return 0;
  1392.         }
  1393.  
  1394.         return 1;
  1395.     }
  1396.  
  1397.     return 0;
  1398. }
  1399.  
  1400. int BotTrace_Duck(bot_state_t *bs, vec3_t traceto)
  1401. {
  1402.     vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod;
  1403.     trace_t tr;
  1404.  
  1405.     VectorSubtract(traceto, bs->origin, a);
  1406.     vectoangles(a, a);
  1407.  
  1408.     AngleVectors(a, fwd, NULL, NULL);
  1409.  
  1410.     traceto_mod[0] = bs->origin[0] + fwd[0]*4;
  1411.     traceto_mod[1] = bs->origin[1] + fwd[1]*4;
  1412.     traceto_mod[2] = bs->origin[2] + fwd[2]*4;
  1413.  
  1414.     mins[0] = -15;
  1415.     mins[1] = -15;
  1416.     mins[2] = -23;
  1417.     maxs[0] = 15;
  1418.     maxs[1] = 15;
  1419.     maxs[2] = 8;
  1420.  
  1421.     trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID);
  1422.  
  1423.     if (tr.fraction != 1)
  1424.     {
  1425.         return 0;
  1426.     }
  1427.  
  1428.     VectorCopy(bs->origin, tracefrom_mod);
  1429.  
  1430.     tracefrom_mod[2] += 31;//33;
  1431.     traceto_mod[2] += 31;//33;
  1432.  
  1433.     mins[0] = -15;
  1434.     mins[1] = -15;
  1435.     mins[2] = 0;
  1436.     maxs[0] = 15;
  1437.     maxs[1] = 15;
  1438.     maxs[2] = 32;
  1439.  
  1440.     trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID);
  1441.  
  1442.     if (tr.fraction != 1)
  1443.     {
  1444.         return 1;
  1445.     }
  1446.  
  1447.     return 0;
  1448. }
  1449.  
  1450. int PassStandardEnemyChecks(bot_state_t *bs, gentity_t *en)
  1451. {
  1452.     if (!bs || !en)
  1453.     {
  1454.         return 0;
  1455.     }
  1456.  
  1457.     if (!en->client)
  1458.     {
  1459.         return 0;
  1460.     }
  1461.  
  1462.     if (en->health < 1)
  1463.     {
  1464.         return 0;
  1465.     }
  1466.  
  1467.     if (!en->takedamage)
  1468.     {
  1469.         return 0;
  1470.     }
  1471.  
  1472.     if (en->client)
  1473.     {
  1474.         if (en->client->ps.pm_type != PM_NORMAL )
  1475.         {
  1476.             return 0;
  1477.         }
  1478.  
  1479.         if ( G_IsClientSpectating ( en->client ) )
  1480.         {
  1481.             return 0;
  1482.         }
  1483.     }
  1484.  
  1485.     if (!en->s.solid)
  1486.     {
  1487.         return 0;
  1488.     }
  1489.  
  1490.     if (bs->client == en->s.number)
  1491.     {
  1492.         return 0;
  1493.     }
  1494.  
  1495.     if (OnSameTeam(&g_entities[bs->client], en))
  1496.     {
  1497.         return 0;
  1498.     }
  1499.  
  1500.     /*
  1501.     if (en->client && en->client->pers.connected != CON_CONNECTED)
  1502.     {
  1503.         return 0;
  1504.     }
  1505.     */
  1506.  
  1507.     return 1;
  1508. }
  1509.  
  1510. void BotDamageNotification(gclient_t *bot, gentity_t *attacker)
  1511. {
  1512.     bot_state_t *bs;
  1513.     bot_state_t *bs_a;
  1514.     int i;
  1515.  
  1516.     if (!bot || !attacker || !attacker->client)
  1517.     {
  1518.         return;
  1519.     }
  1520.  
  1521.     bs_a = botstates[attacker->s.number];
  1522.  
  1523.     if (bs_a)
  1524.     {
  1525.         bs_a->lastAttacked = &g_entities[bot->ps.clientNum];
  1526.         i = 0;
  1527.  
  1528.         while (i < MAX_CLIENTS)
  1529.         {
  1530.             if (botstates[i] &&
  1531.                 i != bs_a->client &&
  1532.                 botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum])
  1533.             {
  1534.                 botstates[i]->lastAttacked = NULL;
  1535.             }
  1536.  
  1537.             i++;
  1538.         }
  1539.     }
  1540.     else //got attacked by a real client, so no one gets rights to lastAttacked
  1541.     {
  1542.         i = 0;
  1543.  
  1544.         while (i < MAX_CLIENTS)
  1545.         {
  1546.             if (botstates[i] &&
  1547.                 botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum])
  1548.             {
  1549.                 botstates[i]->lastAttacked = NULL;
  1550.             }
  1551.  
  1552.             i++;
  1553.         }
  1554.     }
  1555.  
  1556.     bs = botstates[bot->ps.clientNum];
  1557.  
  1558.     if (!bs)
  1559.     {
  1560.         return;
  1561.     }
  1562.  
  1563.     bs->lastHurt = attacker;
  1564.  
  1565.     if (bs->currentEnemy)
  1566.     {
  1567.         return;
  1568.     }
  1569.  
  1570.     if (!PassStandardEnemyChecks(bs, attacker))
  1571.     {
  1572.         return;
  1573.     }
  1574.  
  1575.     if (PassLovedOneCheck(bs, attacker))
  1576.     {
  1577.         bs->currentEnemy = attacker;
  1578.         bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
  1579.     }
  1580. }
  1581.  
  1582. int BotCanHear(bot_state_t *bs, gentity_t *en, float endist)
  1583. {
  1584.     float minlen;
  1585.  
  1586.     if (!en || !en->client)
  1587.     {
  1588.         return 0;
  1589.     }
  1590.  
  1591.     /*
  1592.     if (en && en->client && en->client->ps.otherSoundTime > level.time)
  1593.     {
  1594.         minlen = en->client->ps.otherSoundLen;
  1595.         goto checkStep;
  1596.     }
  1597.     */
  1598.  
  1599.     /*
  1600.     if (en && en->client && en->client->ps.footstepTime > level.time)
  1601.     {
  1602.         minlen = 256;
  1603.         goto checkStep;
  1604.     }
  1605.     */
  1606.  
  1607.     if (gBotEventTracker[en->s.number].eventTime < level.time)
  1608.     {
  1609.         return 0;
  1610.     }
  1611.  
  1612.     switch(gBotEventTracker[en->s.number].events[gBotEventTracker[en->s.number].eventSequence & (MAX_PS_EVENTS-1)])
  1613.     {
  1614.     case EV_GLOBAL_SOUND:
  1615.         minlen = 256;
  1616.         break;
  1617.     case EV_FIRE_WEAPON:
  1618.     case EV_ALT_FIRE:
  1619.         minlen = 512;
  1620.         break;
  1621.     case EV_STEP_4:
  1622.     case EV_STEP_8:
  1623.     case EV_STEP_12:
  1624.     case EV_STEP_16:
  1625.     case EV_FOOTSTEP:
  1626.     case EV_FOOTWADE:
  1627.         minlen = 256;
  1628.         break;
  1629.     case EV_JUMP:
  1630.         minlen = 256;
  1631.         break;
  1632.     default:
  1633.         minlen = 999999;
  1634.         break;
  1635.     }
  1636.  
  1637.     if (endist <= minlen)
  1638.     {
  1639.         return 1;
  1640.     }
  1641.  
  1642.     return 0;
  1643. }
  1644.  
  1645. void UpdateEventTracker(void)
  1646. {
  1647.     int i;
  1648.  
  1649.     i = 0;
  1650.  
  1651.     while (i < MAX_CLIENTS)
  1652.     {
  1653.         if (gBotEventTracker[i].eventSequence != level.clients[i].ps.eventSequence)
  1654.         { //updated event
  1655.             gBotEventTracker[i].eventSequence = level.clients[i].ps.eventSequence;
  1656.             gBotEventTracker[i].events[0] = level.clients[i].ps.events[0];
  1657.             gBotEventTracker[i].events[1] = level.clients[i].ps.events[1];
  1658.             gBotEventTracker[i].eventTime = level.time + 0.5;
  1659.         }
  1660.  
  1661.         i++;
  1662.     }
  1663. }
  1664.  
  1665. int InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
  1666. {
  1667.     int i;
  1668.     float diff, angle;
  1669.  
  1670.     for (i = 0; i < 2; i++)
  1671.     {
  1672.         angle = AngleMod(viewangles[i]);
  1673.         angles[i] = AngleMod(angles[i]);
  1674.         diff = angles[i] - angle;
  1675.         if (angles[i] > angle)
  1676.         {
  1677.             if (diff > 180.0)
  1678.             {
  1679.                 diff -= 360.0;
  1680.             }
  1681.         }
  1682.         else
  1683.         {
  1684.             if (diff < -180.0)
  1685.             {
  1686.                 diff += 360.0;
  1687.             }
  1688.         }
  1689.         if (diff > 0)
  1690.         {
  1691.             if (diff > fov * 0.5)
  1692.             {
  1693.                 return 0;
  1694.             }
  1695.         }
  1696.         else
  1697.         {
  1698.             if (diff < -fov * 0.5)
  1699.             {
  1700.                 return 0;
  1701.             }
  1702.         }
  1703.     }
  1704.     return 1;
  1705. }
  1706.  
  1707. int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent)
  1708. {
  1709.     int i;
  1710.     bot_state_t *loved;
  1711.  
  1712.     if (!bs->lovednum)
  1713.     {
  1714.         return 1;
  1715.     }
  1716.  
  1717.     i = 0;
  1718.  
  1719.     if (!botstates[ent->s.number])
  1720.     { //not a bot
  1721.         return 1;
  1722.     }
  1723.  
  1724.     trap_Cvar_Update(&bot_attachments);
  1725.  
  1726.     if (!bot_attachments.integer)
  1727.     {
  1728.         return 1;
  1729.     }
  1730.  
  1731.     loved = botstates[ent->s.number];
  1732.  
  1733.     while (i < bs->lovednum)
  1734.     {
  1735.         if (strcmp(level.clients[loved->client].pers.netname, bs->loved[i].name) == 0)
  1736.         {
  1737.             if (!IsTeamplay() && bs->loved[i].level < 2)
  1738.             { //if FFA and level of love is not greater than 1, just don't care
  1739.                 return 1;
  1740.             }
  1741.             else if (IsTeamplay() && !OnSameTeam(&g_entities[bs->client], &g_entities[loved->client]) && bs->loved[i].level < 2)
  1742.             { //is teamplay, but not on same team and level < 2
  1743.                 return 1;
  1744.             }
  1745.             else
  1746.             {
  1747.                 return 0;
  1748.             }
  1749.         }
  1750.  
  1751.         i++;
  1752.     }
  1753.  
  1754.     return 1;
  1755. }
  1756.  
  1757. int ScanForEnemies(bot_state_t *bs)
  1758. {
  1759.     vec3_t a;
  1760.     float distcheck;
  1761.     float closest;
  1762.     int bestindex;
  1763.     int i;
  1764.     float hasEnemyDist = 0;
  1765.  
  1766.     closest = 999999;
  1767.     i = 0;
  1768.     bestindex = -1;
  1769.  
  1770.     if (bs->currentEnemy)
  1771.     {
  1772.         hasEnemyDist = bs->frame_Enemy_Len;
  1773.     }
  1774.  
  1775.     while (i <= MAX_CLIENTS)
  1776.     {
  1777.         if (i != bs->client && g_entities[i].client && !OnSameTeam(&g_entities[bs->client], &g_entities[i]) && PassStandardEnemyChecks(bs, &g_entities[i]) && trap_InPVS(g_entities[i].client->ps.origin, bs->eye) && PassLovedOneCheck(bs, &g_entities[i]))
  1778.         {
  1779.             VectorSubtract(g_entities[i].client->ps.origin, bs->eye, a);
  1780.             distcheck = VectorLength(a);
  1781.             vectoangles(a, a);
  1782.  
  1783.             if (distcheck < closest && ((InFieldOfVision(bs->viewangles, 90, a) /*&& !BotMindTricked(bs->client, i)*/) || BotCanHear(bs, &g_entities[i], distcheck)) && OrgVisible(bs->eye, g_entities[i].client->ps.origin, -1))
  1784.             {
  1785.                 if (!hasEnemyDist || distcheck < (hasEnemyDist - 128))
  1786.                 { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out
  1787.                     closest = distcheck;
  1788.                     bestindex = i;
  1789.                 }
  1790.             }
  1791.         }
  1792.         i++;
  1793.     }
  1794.     
  1795.     return bestindex;
  1796. }
  1797.  
  1798. int WaitingForNow(bot_state_t *bs, vec3_t goalpos)
  1799. { //checks if the bot is doing something along the lines of waiting for an elevator to raise up
  1800.     vec3_t xybot, xywp, a;
  1801.  
  1802.     if (!bs->wpCurrent)
  1803.     {
  1804.         return 0;
  1805.     }
  1806.  
  1807.     if ((int)goalpos[0] != (int)bs->wpCurrent->origin[0] ||
  1808.         (int)goalpos[1] != (int)bs->wpCurrent->origin[1] ||
  1809.         (int)goalpos[2] != (int)bs->wpCurrent->origin[2])
  1810.     {
  1811.         return 0;
  1812.     }
  1813.  
  1814.     VectorCopy(bs->origin, xybot);
  1815.     VectorCopy(bs->wpCurrent->origin, xywp);
  1816.  
  1817.     xybot[2] = 0;
  1818.     xywp[2] = 0;
  1819.  
  1820.     VectorSubtract(xybot, xywp, a);
  1821.  
  1822.     if (VectorLength(a) < 16 && bs->frame_Waypoint_Len > 100)
  1823.     {
  1824.         if (CheckForFunc(bs->origin, bs->client))
  1825.         {
  1826.             return 1; //we're probably standing on an elevator and riding up/down. Or at least we hope so.
  1827.         }
  1828.     }
  1829.     else if (VectorLength(a) < 64 && bs->frame_Waypoint_Len > 64 &&
  1830.         CheckForFunc(bs->origin, bs->client))
  1831.     {
  1832.         bs->noUseTime = level.time + 2000;
  1833.     }
  1834.  
  1835.     return 0;
  1836. }
  1837.  
  1838. int BotGetWeaponRange(bot_state_t *bs)
  1839. {
  1840.     switch (weaponData[bs->cur_ps.weapon].category)
  1841.     {
  1842.     case CAT_KNIFE:
  1843.         return BWEAPONRANGE_MELEE;
  1844.     case CAT_PISTOL:
  1845.         return BWEAPONRANGE_MID; //short
  1846.     case CAT_SHOTGUN:     
  1847.         return BWEAPONRANGE_MID; //short
  1848.     case CAT_SUB:         
  1849.         return BWEAPONRANGE_MID;
  1850.     case CAT_ASSAULT:     
  1851.         return BWEAPONRANGE_MID;
  1852.     case CAT_SNIPER:      
  1853.         return BWEAPONRANGE_LONG;
  1854.     case CAT_HEAVY:       
  1855.         return BWEAPONRANGE_LONG;
  1856.     case CAT_GRENADE:     
  1857.         return BWEAPONRANGE_MID; //short
  1858.     default:
  1859.         return BWEAPONRANGE_MID;
  1860.     }
  1861. }
  1862.  
  1863. int BotIsAChickenWuss(bot_state_t *bs)
  1864. {
  1865.     int bWRange;
  1866.  
  1867.     if (bs->chickenWussCalculationTime > level.time)
  1868.     {
  1869.         return 2; //don't want to keep going between two points...
  1870.     }
  1871.  
  1872.     bs->chickenWussCalculationTime = level.time + MAX_CHICKENWUSS_TIME;
  1873.  
  1874.     if (g_entities[bs->client].health < BOT_RUN_HEALTH)
  1875.     {
  1876.         return 1;
  1877.     }
  1878.  
  1879.     bWRange = BotGetWeaponRange(bs);
  1880.  
  1881.     if (bWRange == BWEAPONRANGE_MELEE)
  1882.     {
  1883.         if (!bs->meleeSpecialist)
  1884.         {
  1885.             return 1;
  1886.         }
  1887.     }
  1888.  
  1889.     if (bs->cur_ps.weapon < WP_USAS_12_SHOTGUN)
  1890.     { //the bryar is a weak weapon, so just try to find a new one if it's what you're having to use
  1891.         return 1;
  1892.     }
  1893.  
  1894.     if (bs->currentEnemy && bs->currentEnemy->client &&
  1895.         bs->currentEnemy->client->ps.weapon == WP_KNIFE &&
  1896.         bs->frame_Enemy_Len < 512 && bs->cur_ps.weapon != WP_KNIFE)
  1897.     { //if close to an enemy with a knife and not using a knife, then try to back off
  1898.         return 1;
  1899.     }
  1900.  
  1901.     //didn't run, reset the timer
  1902.     bs->chickenWussCalculationTime = 0;
  1903.  
  1904.     return 0;
  1905. }
  1906.  
  1907. gentity_t *GetNearestBadThing(bot_state_t *bs)
  1908. {
  1909.     int i = 0;
  1910.     float glen;
  1911.     vec3_t hold;
  1912.     int bestindex = 0;
  1913.     float bestdist = 800; //if not within a radius of 800, it's no threat anyway
  1914.     int foundindex = 0;
  1915.     float factor = 0;
  1916.     gentity_t *ent;
  1917.     trace_t tr;
  1918.  
  1919.     while (i < MAX_GENTITIES)
  1920.     {
  1921.         ent = &g_entities[i];
  1922.  
  1923.         if ( (ent &&
  1924.             !ent->client &&
  1925.             ent->inuse &&
  1926.             ent->damage &&
  1927.             /*(ent->s.weapon == WP_THERMAL || ent->s.weapon == WP_FLECHETTE)*/
  1928.             ent->s.weapon &&
  1929.             ent->splashDamage) )
  1930.         { //try to escape from anything with a non-0 s.weapon and non-0 damage. This hopefully only means dangerous projectiles.
  1931.           //Or a sentry gun if bolt_Head == 1000. This is a terrible hack, yes.
  1932.             VectorSubtract(bs->origin, ent->r.currentOrigin, hold);
  1933.             glen = VectorLength(hold);
  1934.  
  1935.             //if (ent->s.weapon != WP_THERMAL && ent->s.weapon != WP_FLECHETTE &&
  1936.             //    ent->s.weapon != WP_DET_PACK && ent->s.weapon != WP_TRIP_MINE)
  1937.             if (weaponData[ent->s.weapon].category != CAT_GRENADE)
  1938.             {
  1939.                 factor = 0.5;
  1940.             }
  1941.             else
  1942.             {
  1943.                 factor = 1;
  1944.             }
  1945.  
  1946.             if (ent->s.weapon == WP_RPG7_LAUNCHER &&
  1947.                 (ent->r.ownerNum == bs->client ||
  1948.                 (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS &&
  1949.                 g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) )
  1950.             { //don't be afraid of your own rockets or your teammates' rockets
  1951.                 factor = 0;
  1952.             }
  1953.  
  1954.             if (glen < bestdist*factor && trap_InPVS(bs->origin, ent->s.pos.trBase))
  1955.             {
  1956.                 trap_Trace(&tr, bs->origin, NULL, NULL, ent->s.pos.trBase, bs->client, MASK_SOLID);
  1957.  
  1958.                 if (tr.fraction == 1 || tr.entityNum == ent->s.number)
  1959.                 {
  1960.                     bestindex = i;
  1961.                     bestdist = glen;
  1962.                     foundindex = 1;
  1963.                 }
  1964.             }
  1965.         }
  1966.  
  1967.         i++;
  1968.     }
  1969.  
  1970.     if (foundindex)
  1971.     {
  1972.         bs->dontGoBack = level.time + 1500;
  1973.         return &g_entities[bestindex];
  1974.     }
  1975.     else
  1976.     {
  1977.         return NULL;
  1978.     }
  1979. }
  1980.  
  1981. int BotDefendFlag(bot_state_t *bs)
  1982. {
  1983.     wpobject_t *flagPoint;
  1984.     vec3_t a;
  1985.  
  1986.     if (level.clients[bs->client].sess.team == TEAM_RED)
  1987.     {
  1988.         flagPoint = flagRed;
  1989.     }
  1990.     else if (level.clients[bs->client].sess.team == TEAM_BLUE)
  1991.     {
  1992.         flagPoint = flagBlue;
  1993.     }
  1994.     else
  1995.     {
  1996.         return 0;
  1997.     }
  1998.  
  1999.     if (!flagPoint)
  2000.     {
  2001.         return 0;
  2002.     }
  2003.  
  2004.     VectorSubtract(bs->origin, flagPoint->origin, a);
  2005.  
  2006.     if (VectorLength(a) > BASE_GUARD_DISTANCE)
  2007.     {
  2008.         bs->wpDestination = flagPoint;
  2009.     }
  2010.  
  2011.     return 1;
  2012. }
  2013.  
  2014. int BotGetEnemyFlag(bot_state_t *bs)
  2015. {
  2016.     wpobject_t *flagPoint;
  2017.     vec3_t a;
  2018.  
  2019.     if (level.clients[bs->client].sess.team == TEAM_RED)
  2020.     {
  2021.         flagPoint = flagBlue;
  2022.     }
  2023.     else if (level.clients[bs->client].sess.team == TEAM_BLUE)
  2024.     {
  2025.         flagPoint = flagRed;
  2026.     }
  2027.     else
  2028.     {
  2029.         return 0;
  2030.     }
  2031.  
  2032.     if (!flagPoint)
  2033.     {
  2034.         return 0;
  2035.     }
  2036.  
  2037.     VectorSubtract(bs->origin, flagPoint->origin, a);
  2038.  
  2039.     if (VectorLength(a) > BASE_GETENEMYFLAG_DISTANCE)
  2040.     {
  2041.         bs->wpDestination = flagPoint;
  2042.     }
  2043.  
  2044.     return 1;
  2045. }
  2046.  
  2047. int BotGetFlagBack(bot_state_t *bs)
  2048. {
  2049. #ifdef BOT_KNOW_CTF
  2050.     int i = 0;
  2051.     int myFlag = 0;
  2052.     int foundCarrier = 0;
  2053.     int tempInt = 0;
  2054.     gentity_t *ent = NULL;
  2055.     vec3_t usethisvec;
  2056.  
  2057.     if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
  2058.     {
  2059.         myFlag = PW_REDFLAG;
  2060.     }
  2061.     else
  2062.     {
  2063.         myFlag = PW_BLUEFLAG;
  2064.     }
  2065.  
  2066.     while (i < MAX_CLIENTS)
  2067.     {
  2068.         ent = &g_entities[i];
  2069.  
  2070.         if (ent && ent->client && ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
  2071.         {
  2072.             foundCarrier = 1;
  2073.             break;
  2074.         }
  2075.  
  2076.         i++;
  2077.     }
  2078.  
  2079.     if (!foundCarrier)
  2080.     {
  2081.         return 0;
  2082.     }
  2083.  
  2084.     if (!ent)
  2085.     {
  2086.         return 0;
  2087.     }
  2088.  
  2089.     if (bs->wpDestSwitchTime < level.time)
  2090.     {
  2091.         if (ent->client)
  2092.         {
  2093.             VectorCopy(ent->client->ps.origin, usethisvec);
  2094.         }
  2095.         else
  2096.         {
  2097.             VectorCopy(ent->s.origin, usethisvec);
  2098.         }
  2099.  
  2100.         tempInt = GetNearestVisibleWP(usethisvec, 0);
  2101.  
  2102.         if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
  2103.         {
  2104.             bs->wpDestination = gWPArray[tempInt];
  2105.             bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
  2106.         }
  2107.     }
  2108.  
  2109.     return 1;
  2110. #else
  2111.     return 0;
  2112. #endif
  2113. }
  2114.  
  2115. int BotGuardFlagCarrier(bot_state_t *bs)
  2116. {
  2117. #ifdef BOT_KNOW_CTF
  2118.     int i = 0;
  2119.     int enemyFlag = 0;
  2120.     int foundCarrier = 0;
  2121.     int tempInt = 0;
  2122.     gentity_t *ent = NULL;
  2123.     vec3_t usethisvec;
  2124.  
  2125.     if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
  2126.     {
  2127.         enemyFlag = PW_BLUEFLAG;
  2128.     }
  2129.     else
  2130.     {
  2131.         enemyFlag = PW_REDFLAG;
  2132.     }
  2133.  
  2134.     while (i < MAX_CLIENTS)
  2135.     {
  2136.         ent = &g_entities[i];
  2137.  
  2138.         if (ent && ent->client && ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
  2139.         {
  2140.             foundCarrier = 1;
  2141.             break;
  2142.         }
  2143.  
  2144.         i++;
  2145.     }
  2146.  
  2147.     if (!foundCarrier)
  2148.     {
  2149.         return 0;
  2150.     }
  2151.  
  2152.     if (!ent)
  2153.     {
  2154.         return 0;
  2155.     }
  2156.  
  2157.     if (bs->wpDestSwitchTime < level.time)
  2158.     {
  2159.         if (ent->client)
  2160.         {
  2161.             VectorCopy(ent->client->ps.origin, usethisvec);
  2162.         }
  2163.         else
  2164.         {
  2165.             VectorCopy(ent->s.origin, usethisvec);
  2166.         }
  2167.  
  2168.         tempInt = GetNearestVisibleWP(usethisvec, 0);
  2169.  
  2170.         if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
  2171.         {
  2172.             bs->wpDestination = gWPArray[tempInt];
  2173.             bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
  2174.         }
  2175.     }
  2176.  
  2177.     return 1;
  2178. #else
  2179.     return 0;
  2180. #endif
  2181. }
  2182.  
  2183. int BotGetFlagHome(bot_state_t *bs)
  2184. {
  2185. #ifdef BOT_KNOW_CTF
  2186.     wpobject_t *flagPoint;
  2187.     vec3_t a;
  2188.  
  2189.     if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
  2190.     {
  2191.         flagPoint = flagRed;
  2192.     }
  2193.     else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE)
  2194.     {
  2195.         flagPoint = flagBlue;
  2196.     }
  2197.     else
  2198.     {
  2199.         return 0;
  2200.     }
  2201.  
  2202.     if (!flagPoint)
  2203.     {
  2204.         return 0;
  2205.     }
  2206.  
  2207.     VectorSubtract(bs->origin, flagPoint->origin, a);
  2208.  
  2209.     if (VectorLength(a) > BASE_FLAGWAIT_DISTANCE)
  2210.     {
  2211.         bs->wpDestination = flagPoint;
  2212.     }
  2213.  
  2214.     return 1;
  2215. #else
  2216.     return 0;
  2217. #endif
  2218. }
  2219.  
  2220. void GetNewFlagPoint(wpobject_t *wp, gentity_t *flagEnt, int team)
  2221. { //get the nearest possible waypoint to the flag since it's not in its original position
  2222. #ifdef BOT_KNOW_CTF
  2223.     int i = 0;
  2224.     vec3_t a, mins, maxs;
  2225.     float bestdist;
  2226.     float testdist;
  2227.     int bestindex = 0;
  2228.     int foundindex = 0;
  2229.     trace_t tr;
  2230.  
  2231.     mins[0] = -15;
  2232.     mins[1] = -15;
  2233.     mins[2] = -5;
  2234.     maxs[0] = 15;
  2235.     maxs[1] = 15;
  2236.     maxs[2] = 5;
  2237.  
  2238.     VectorSubtract(wp->origin, flagEnt->s.pos.trBase, a);
  2239.  
  2240.     bestdist = VectorLength(a);
  2241.  
  2242.     if (bestdist <= WP_KEEP_FLAG_DIST)
  2243.     {
  2244.         trap_Trace(&tr, wp->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID);
  2245.  
  2246.         if (tr.fraction == 1)
  2247.         { //this point is good
  2248.             return;
  2249.         }
  2250.     }
  2251.  
  2252.     while (i < gWPNum)
  2253.     {
  2254.         VectorSubtract(gWPArray[i]->origin, flagEnt->s.pos.trBase, a);
  2255.         testdist = VectorLength(a);
  2256.  
  2257.         if (testdist < bestdist)
  2258.         {
  2259.             trap_Trace(&tr, gWPArray[i]->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID);
  2260.  
  2261.             if (tr.fraction == 1)
  2262.             {
  2263.                 foundindex = 1;
  2264.                 bestindex = i;
  2265.                 bestdist = testdist;
  2266.             }
  2267.         }
  2268.  
  2269.         i++;
  2270.     }
  2271.  
  2272.     if (foundindex)
  2273.     {
  2274.         if (team == TEAM_RED)
  2275.         {
  2276.             flagRed = gWPArray[bestindex];
  2277.         }
  2278.         else
  2279.         {
  2280.             flagBlue = gWPArray[bestindex];
  2281.         }
  2282.     }
  2283. #endif
  2284. }
  2285.  
  2286. int CTFTakesPriority(bot_state_t *bs)
  2287. {
  2288. /*
  2289. #ifdef BOT_KNOW_CTF
  2290.     gentity_t *ent = NULL;
  2291.     int enemyFlag = 0;
  2292.     int myFlag = 0;
  2293.     int enemyHasOurFlag = 0;
  2294.     int weHaveEnemyFlag = 0;
  2295.     int numOnMyTeam = 0;
  2296.     int numOnEnemyTeam = 0;
  2297.     int numAttackers = 0;
  2298.     int numDefenders = 0;
  2299.     int i = 0;
  2300.     int idleWP;
  2301.     int dosw = 0;
  2302.     wpobject_t *dest_sw = NULL;
  2303. #ifdef BOT_CTF_DEBUG
  2304.     vec3_t t;
  2305.  
  2306.     G_Printf("CTFSTATE: %s\n", ctfStateNames[bs->ctfState]);
  2307. #endif
  2308.  
  2309.     if (level.gametype != GT_CTF)
  2310.     {
  2311.         return 0;
  2312.     }
  2313.  
  2314.     if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
  2315.         (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME)
  2316.     { //get the nearest weapon laying around base before heading off for battle
  2317.         idleWP = GetBestIdleGoal(bs);
  2318.  
  2319.         if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
  2320.         {
  2321.             if (bs->wpDestSwitchTime < level.time)
  2322.             {
  2323.                 bs->wpDestination = gWPArray[idleWP];
  2324.             }
  2325.             return 1;
  2326.         }
  2327.     }
  2328.     else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
  2329.         (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_CTF &&
  2330.         bs->wpDestination && bs->wpDestination->weight)
  2331.     {
  2332.         dest_sw = bs->wpDestination;
  2333.         dosw = 1;
  2334.     }
  2335.  
  2336.     if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
  2337.     {
  2338.         myFlag = PW_REDFLAG;
  2339.     }
  2340.     else
  2341.     {
  2342.         myFlag = PW_BLUEFLAG;
  2343.     }
  2344.  
  2345.     if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
  2346.     {
  2347.         enemyFlag = PW_BLUEFLAG;
  2348.     }
  2349.     else
  2350.     {
  2351.         enemyFlag = PW_REDFLAG;
  2352.     }
  2353.  
  2354.     if (!flagRed || !flagBlue ||
  2355.         !flagRed->inuse || !flagBlue->inuse ||
  2356.         !eFlagRed || !eFlagBlue)
  2357.     {
  2358.         return 0;
  2359.     }
  2360.  
  2361. #ifdef BOT_CTF_DEBUG
  2362.     VectorCopy(flagRed->origin, t);
  2363.     t[2] += 128;
  2364.     G_TestLine(flagRed->origin, t, 0x0000ff, 500);
  2365.  
  2366.     VectorCopy(flagBlue->origin, t);
  2367.     t[2] += 128;
  2368.     G_TestLine(flagBlue->origin, t, 0x0000ff, 500);
  2369. #endif
  2370.  
  2371.     if (droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM))
  2372.     {
  2373.         GetNewFlagPoint(flagRed, droppedRedFlag, TEAM_RED);
  2374.     }
  2375.     else
  2376.     {
  2377.         flagRed = oFlagRed;
  2378.     }
  2379.  
  2380.     if (droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM))
  2381.     {
  2382.         GetNewFlagPoint(flagBlue, droppedBlueFlag, TEAM_BLUE);
  2383.     }
  2384.     else
  2385.     {
  2386.         flagBlue = oFlagBlue;
  2387.     }
  2388.  
  2389.     if (!bs->ctfState)
  2390.     {
  2391.         return 0;
  2392.     }
  2393.  
  2394.     i = 0;
  2395.  
  2396.     while (i < MAX_CLIENTS)
  2397.     {
  2398.         ent = &g_entities[i];
  2399.  
  2400.         if (ent && ent->client)
  2401.         {
  2402.             if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
  2403.             {
  2404.                 weHaveEnemyFlag = 1;
  2405.             }
  2406.             else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
  2407.             {
  2408.                 enemyHasOurFlag = 1;
  2409.             }
  2410.  
  2411.             if (OnSameTeam(&g_entities[bs->client], ent))
  2412.             {
  2413.                 numOnMyTeam++;
  2414.             }
  2415.             else
  2416.             {
  2417.                 numOnEnemyTeam++;
  2418.             }
  2419.  
  2420.             if (botstates[ent->s.number])
  2421.             {
  2422.                 if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER ||
  2423.                     botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL)
  2424.                 {
  2425.                     numAttackers++;
  2426.                 }
  2427.                 else
  2428.                 {
  2429.                     numDefenders++;
  2430.                 }
  2431.             }
  2432.             else
  2433.             { //assume real players to be attackers in our logic
  2434.                 numAttackers++;
  2435.             }
  2436.         }
  2437.         i++;
  2438.     }
  2439.  
  2440.     if (bs->cur_ps.powerups[enemyFlag])
  2441.     {
  2442.         if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag)
  2443.         {
  2444.             bs->ctfState = CTFSTATE_RETRIEVAL;
  2445.         }
  2446.         else
  2447.         {
  2448.             bs->ctfState = CTFSTATE_GETFLAGHOME;
  2449.         }
  2450.     }
  2451.     else if (bs->ctfState == CTFSTATE_GETFLAGHOME)
  2452.     {
  2453.         bs->ctfState = 0;
  2454.     }
  2455.  
  2456.     if (bs->state_Forced)
  2457.     {
  2458.         bs->ctfState = bs->state_Forced;
  2459.     }
  2460.  
  2461.     if (bs->ctfState == CTFSTATE_DEFENDER)
  2462.     {
  2463.         if (BotDefendFlag(bs))
  2464.         {
  2465.             goto success;
  2466.         }
  2467.     }
  2468.  
  2469.     if (bs->ctfState == CTFSTATE_ATTACKER)
  2470.     {
  2471.         if (BotGetEnemyFlag(bs))
  2472.         {
  2473.             goto success;
  2474.         }
  2475.     }
  2476.  
  2477.     if (bs->ctfState == CTFSTATE_RETRIEVAL)
  2478.     {
  2479.         if (BotGetFlagBack(bs))
  2480.         {
  2481.             goto success;
  2482.         }
  2483.         else
  2484.         { //can't find anyone on another team being a carrier, so ignore this priority
  2485.             bs->ctfState = 0;
  2486.         }
  2487.     }
  2488.  
  2489.     if (bs->ctfState == CTFSTATE_GUARDCARRIER)
  2490.     {
  2491.         if (BotGuardFlagCarrier(bs))
  2492.         {
  2493.             goto success;
  2494.         }
  2495.         else
  2496.         { //can't find anyone on our team being a carrier, so ignore this priority
  2497.             bs->ctfState = 0;
  2498.         }
  2499.     }
  2500.  
  2501.     if (bs->ctfState == CTFSTATE_GETFLAGHOME)
  2502.     {
  2503.         if (BotGetFlagHome(bs))
  2504.         {
  2505.             goto success;
  2506.         }
  2507.     }
  2508.  
  2509.     return 0;
  2510.  
  2511. success:
  2512.     if (dosw)
  2513.     { //allow ctf code to run, but if after a particular item then keep going after it
  2514.         bs->wpDestination = dest_sw;
  2515.     }
  2516.  
  2517.     return 1;
  2518. #else
  2519.     return 0;
  2520. #endif
  2521. */
  2522.     return 0;
  2523. }
  2524.  
  2525. int EntityVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore, int ignore2)
  2526. {
  2527.     trace_t tr;
  2528.  
  2529.     trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID);
  2530.  
  2531.     if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
  2532.     {
  2533.         return 1;
  2534.     }
  2535.     else if (tr.entityNum != ENTITYNUM_NONE && tr.entityNum == ignore2)
  2536.     {
  2537.         return 1;
  2538.     }
  2539.  
  2540.     return 0;
  2541. }
  2542.  
  2543. int BotHasAssociated(bot_state_t *bs, wpobject_t *wp)
  2544. {
  2545.     gentity_t *as;
  2546.  
  2547.     if (wp->associated_entity == ENTITYNUM_NONE)
  2548.     { //make it think this is an item we have so we don't go after nothing
  2549.         return 1;
  2550.     }
  2551.  
  2552.     as = &g_entities[wp->associated_entity];
  2553.  
  2554.     if (!as || !as->item)
  2555.     {
  2556.         return 0;
  2557.     }
  2558.  
  2559.     if (as->item->giType == IT_WEAPON)
  2560.     {
  2561.         if (bs->cur_ps.stats[STAT_WEAPONS] & (1 << as->item->giTag))
  2562.         {
  2563.             return 1;
  2564.         }
  2565.  
  2566.         return 0;
  2567.     }
  2568. #ifdef BOT_USE_HOLDABLE
  2569.     else if (as->item->giType == IT_HOLDABLE)
  2570.     {
  2571.         if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << as->item->giTag))
  2572.         {
  2573.             return 1;
  2574.         }
  2575.  
  2576.         return 0;
  2577.     }
  2578. #endif
  2579.     else if (as->item->giType == IT_AMMO)
  2580.     {
  2581.         if (bs->cur_ps.ammo[as->item->giTag] > 10) //hack
  2582.         {
  2583.             return 1;
  2584.         }
  2585.  
  2586.         return 0;
  2587.     }
  2588.  
  2589.     return 0;
  2590. }
  2591.  
  2592. int GetBestIdleGoal(bot_state_t *bs)
  2593. {
  2594.     int i = 0;
  2595.     int highestweight = 0;
  2596.     int desiredindex = -1;
  2597.     int dist_to_weight = 0;
  2598.     int traildist;
  2599.  
  2600.     if (!bs->wpCurrent)
  2601.     {
  2602.         return -1;
  2603.     }
  2604.  
  2605.     if (bs->isCamper != 2)
  2606.     {
  2607.         if (bs->randomNavTime < level.time)
  2608.         {
  2609.             if (Q_irand(1, 10) < 5)
  2610.             {
  2611.                 bs->randomNav = 1;
  2612.             }
  2613.             else
  2614.             {
  2615.                 bs->randomNav = 0;
  2616.             }
  2617.             
  2618.             bs->randomNavTime = level.time + Q_irand(5000, 15000);
  2619.         }
  2620.     }
  2621.  
  2622.     if (bs->randomNav)
  2623.     { //stop looking for items and/or camping on them
  2624.         return -1;
  2625.     }
  2626.  
  2627.     while (i < gWPNum)
  2628.     {
  2629.         if (gWPArray[i] &&
  2630.             gWPArray[i]->inuse &&
  2631.             (gWPArray[i]->flags & WPFLAG_GOALPOINT) &&
  2632.             gWPArray[i]->weight > highestweight &&
  2633.             !BotHasAssociated(bs, gWPArray[i]))
  2634.         {
  2635.             traildist = TotalTrailDistance(bs->wpCurrent->index, i, bs);
  2636.  
  2637.             if (traildist != -1)
  2638.             {
  2639.                 dist_to_weight = (int)traildist/10000;
  2640.                 dist_to_weight = (gWPArray[i]->weight)-dist_to_weight;
  2641.  
  2642.                 if (dist_to_weight > highestweight)
  2643.                 {
  2644.                     highestweight = dist_to_weight;
  2645.                     desiredindex = i;
  2646.                 }
  2647.             }
  2648.         }
  2649.  
  2650.         i++;
  2651.     }
  2652.  
  2653.     return desiredindex;
  2654. }
  2655.  
  2656. void GetIdealDestination(bot_state_t *bs)
  2657. {
  2658.     int tempInt, cWPIndex, bChicken, idleWP;
  2659.     float distChange, plusLen, minusLen;
  2660.     vec3_t usethisvec, a;
  2661.     gentity_t *badthing;
  2662.  
  2663.     if (!bs->wpCurrent)
  2664.     {
  2665.         return;
  2666.     }
  2667.  
  2668.     if ((level.time - bs->escapeDirTime) > 4000)
  2669.     {
  2670.         badthing = GetNearestBadThing(bs);
  2671.     }
  2672.     else
  2673.     {
  2674.         badthing = NULL;
  2675.     }
  2676.  
  2677.     if (badthing && badthing->inuse &&
  2678.         badthing->health > 0 && badthing->takedamage)
  2679.     {
  2680.         bs->dangerousObject = badthing;
  2681.     }
  2682.     else
  2683.     {
  2684.         bs->dangerousObject = NULL;
  2685.     }
  2686.  
  2687.     if (!badthing && bs->wpDestIgnoreTime > level.time)
  2688.     {
  2689.         return;
  2690.     }
  2691.  
  2692.     if (!badthing && bs->dontGoBack > level.time)
  2693.     {
  2694.         if (bs->wpDestination)
  2695.         {
  2696.             bs->wpStoreDest = bs->wpDestination;
  2697.         }
  2698.         bs->wpDestination = NULL;
  2699.         return;
  2700.     }
  2701.     else if (!badthing && bs->wpStoreDest)
  2702.     { //after we finish running away, switch back to our original destination
  2703.         bs->wpDestination = bs->wpStoreDest;
  2704.         bs->wpStoreDest = NULL;
  2705.     }
  2706.  
  2707.     if (badthing && bs->wpCamping)
  2708.     {
  2709.         bs->wpCamping = NULL;
  2710.     }
  2711.  
  2712.     if (bs->wpCamping)
  2713.     {
  2714.         bs->wpDestination = bs->wpCamping;
  2715.         return;
  2716.     }
  2717.  
  2718.     if (!badthing && CTFTakesPriority(bs))
  2719.     {
  2720.         if (bs->ctfState)
  2721.         {
  2722.             bs->runningToEscapeThreat = 1;
  2723.         }
  2724.         return;
  2725.     }
  2726.  
  2727.     if (badthing)
  2728.     {
  2729.         bs->runningLikeASissy = level.time + 100;
  2730.  
  2731.         if (bs->wpDestination)
  2732.         {
  2733.             bs->wpStoreDest = bs->wpDestination;
  2734.         }
  2735.         bs->wpDestination = NULL;
  2736.  
  2737.         if (bs->wpDirection)
  2738.         {
  2739.             tempInt = bs->wpCurrent->index+1;
  2740.         }
  2741.         else
  2742.         {
  2743.             tempInt = bs->wpCurrent->index-1;
  2744.         }
  2745.  
  2746.         if (gWPArray[tempInt] && gWPArray[tempInt]->inuse && bs->escapeDirTime < level.time)
  2747.         {
  2748.             VectorSubtract(badthing->s.pos.trBase, bs->wpCurrent->origin, a);
  2749.             plusLen = VectorLength(a);
  2750.             VectorSubtract(badthing->s.pos.trBase, gWPArray[tempInt]->origin, a);
  2751.             minusLen = VectorLength(a);
  2752.  
  2753.             if (plusLen < minusLen)
  2754.             {
  2755.                 if (bs->wpDirection)
  2756.                 {
  2757.                     bs->wpDirection = 0;
  2758.                 }
  2759.                 else
  2760.                 {
  2761.                     bs->wpDirection = 1;
  2762.                 }
  2763.  
  2764.                 bs->wpCurrent = gWPArray[tempInt];
  2765.  
  2766.                 bs->escapeDirTime = level.time + Q_irand(500, 1000);//Q_irand(1000, 1400);
  2767.  
  2768.                 //G_Printf("Escaping from scary bad thing [%s]\n", badthing->classname);
  2769.             }
  2770.         }
  2771.         //G_Printf("Run away run away run away!\n");
  2772.         return;
  2773.     }
  2774.  
  2775.     distChange = 0; //keep the compiler from complaining
  2776.  
  2777.     tempInt = BotGetWeaponRange(bs);
  2778.  
  2779.     if (tempInt == BWEAPONRANGE_MELEE)
  2780.     {
  2781.         distChange = 1;
  2782.     }
  2783.     else if (tempInt == BWEAPONRANGE_SABER)
  2784.     {
  2785.         distChange = 1;
  2786.     }
  2787.     else if (tempInt == BWEAPONRANGE_MID)
  2788.     {
  2789.         distChange = 128;
  2790.     }
  2791.     else if (tempInt == BWEAPONRANGE_LONG)
  2792.     {
  2793.         distChange = 300;
  2794.     }
  2795.  
  2796.     if (bs->revengeEnemy && bs->revengeEnemy->health > 0 &&
  2797.         bs->revengeEnemy->client && (bs->revengeEnemy->client->pers.connected == CA_ACTIVE || bs->revengeEnemy->client->pers.connected == CA_AUTHORIZING))
  2798.     { //if we hate someone, always try to get to them
  2799.         if (bs->wpDestSwitchTime < level.time)
  2800.         {
  2801.             if (bs->revengeEnemy->client)
  2802.             {
  2803.                 VectorCopy(bs->revengeEnemy->client->ps.origin, usethisvec);
  2804.             }
  2805.             else
  2806.             {
  2807.                 VectorCopy(bs->revengeEnemy->s.origin, usethisvec);
  2808.             }
  2809.  
  2810.             tempInt = GetNearestVisibleWP(usethisvec, 0);
  2811.  
  2812.             if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
  2813.             {
  2814.                 bs->wpDestination = gWPArray[tempInt];
  2815.                 bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000);
  2816.             }
  2817.         }
  2818.     }
  2819.     else if (bs->squadLeader && bs->squadLeader->health > 0 &&
  2820.         bs->squadLeader->client && (bs->squadLeader->client->pers.connected == CA_ACTIVE || bs->squadLeader->client->pers.connected == CA_AUTHORIZING))
  2821.     {
  2822.         if (bs->wpDestSwitchTime < level.time)
  2823.         {
  2824.             if (bs->squadLeader->client)
  2825.             {
  2826.                 VectorCopy(bs->squadLeader->client->ps.origin, usethisvec);
  2827.             }
  2828.             else
  2829.             {
  2830.                 VectorCopy(bs->squadLeader->s.origin, usethisvec);
  2831.             }
  2832.  
  2833.             tempInt = GetNearestVisibleWP(usethisvec, 0);
  2834.  
  2835.             if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
  2836.             {
  2837.                 bs->wpDestination = gWPArray[tempInt];
  2838.                 bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000);
  2839.             }
  2840.         }
  2841.     }
  2842.     else if (bs->currentEnemy)
  2843.     {
  2844.         if (bs->currentEnemy->client)
  2845.         {
  2846.             VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
  2847.         }
  2848.         else
  2849.         {
  2850.             VectorCopy(bs->currentEnemy->s.origin, usethisvec);
  2851.         }
  2852.  
  2853.         bChicken = BotIsAChickenWuss(bs);
  2854.         bs->runningToEscapeThreat = bChicken;
  2855.  
  2856.         if (bs->frame_Enemy_Len < distChange || (bChicken && bChicken != 2))
  2857.         {
  2858.             cWPIndex = bs->wpCurrent->index;
  2859.  
  2860.             if (bs->frame_Enemy_Len > 400)
  2861.             { //good distance away, start running toward a good place for an item or powerup or whatever
  2862.                 idleWP = GetBestIdleGoal(bs);
  2863.  
  2864.                 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
  2865.                 {
  2866.                     bs->wpDestination = gWPArray[idleWP];
  2867.                 }
  2868.             }
  2869.             else if (gWPArray[cWPIndex-1] && gWPArray[cWPIndex-1]->inuse &&
  2870.                 gWPArray[cWPIndex+1] && gWPArray[cWPIndex+1]->inuse)
  2871.             {
  2872.                 VectorSubtract(gWPArray[cWPIndex+1]->origin, usethisvec, a);
  2873.                 plusLen = VectorLength(a);
  2874.                 VectorSubtract(gWPArray[cWPIndex-1]->origin, usethisvec, a);
  2875.                 minusLen = VectorLength(a);
  2876.  
  2877.                 if (minusLen > plusLen)
  2878.                 {
  2879.                     bs->wpDestination = gWPArray[cWPIndex-1];
  2880.                 }
  2881.                 else
  2882.                 {
  2883.                     bs->wpDestination = gWPArray[cWPIndex+1];
  2884.                 }
  2885.             }
  2886.         }
  2887.         else if (bChicken != 2 && bs->wpDestSwitchTime < level.time)
  2888.         {
  2889.             tempInt = GetNearestVisibleWP(usethisvec, 0);
  2890.  
  2891.             if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
  2892.             {
  2893.                 bs->wpDestination = gWPArray[tempInt];
  2894.                 bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
  2895.             }
  2896.         }
  2897.     }
  2898.  
  2899.     if (!bs->wpDestination && bs->wpDestSwitchTime < level.time)
  2900.     {
  2901.         //G_Printf("I need something to do\n");
  2902.         idleWP = GetBestIdleGoal(bs);
  2903.  
  2904.         if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
  2905.         {
  2906.             bs->wpDestination = gWPArray[idleWP];
  2907.         }
  2908.     }
  2909. }
  2910.  
  2911. void CommanderBotCTFAI(bot_state_t *bs)
  2912. {
  2913. #ifdef BOT_KNOW_CTF
  2914.     int i = 0;
  2915.     gentity_t *ent;
  2916.     int squadmates = 0;
  2917.     gentity_t *squad[MAX_CLIENTS];
  2918.     int defendAttackPriority = 0; //0 == attack, 1 == defend
  2919.     int guardDefendPriority = 0; //0 == defend, 1 == guard
  2920.     int attackRetrievePriority = 0; //0 == retrieve, 1 == attack
  2921.     int myFlag = 0;
  2922.     int enemyFlag = 0;
  2923.     int enemyHasOurFlag = 0;
  2924.     int weHaveEnemyFlag = 0;
  2925.     int numOnMyTeam = 0;
  2926.     int numOnEnemyTeam = 0;
  2927.     int numAttackers = 0;
  2928.     int numDefenders = 0;
  2929.  
  2930.     if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
  2931.     {
  2932.         myFlag = PW_REDFLAG;
  2933.     }
  2934.     else
  2935.     {
  2936.         myFlag = PW_BLUEFLAG;
  2937.     }
  2938.  
  2939.     if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
  2940.     {
  2941.         enemyFlag = PW_BLUEFLAG;
  2942.     }
  2943.     else
  2944.     {
  2945.         enemyFlag = PW_REDFLAG;
  2946.     }
  2947.  
  2948.     while (i < MAX_CLIENTS)
  2949.     {
  2950.         ent = &g_entities[i];
  2951.  
  2952.         if (ent && ent->client)
  2953.         {
  2954.             if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
  2955.             {
  2956.                 weHaveEnemyFlag = 1;
  2957.             }
  2958.             else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
  2959.             {
  2960.                 enemyHasOurFlag = 1;
  2961.             }
  2962.  
  2963.             if (OnSameTeam(&g_entities[bs->client], ent))
  2964.             {
  2965.                 numOnMyTeam++;
  2966.             }
  2967.             else
  2968.             {
  2969.                 numOnEnemyTeam++;
  2970.             }
  2971.  
  2972.             if (botstates[ent->s.number])
  2973.             {
  2974.                 if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER ||
  2975.                     botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL)
  2976.                 {
  2977.                     numAttackers++;
  2978.                 }
  2979.                 else
  2980.                 {
  2981.                     numDefenders++;
  2982.                 }
  2983.             }
  2984.             else
  2985.             { //assume real players to be attackers in our logic
  2986.                 numAttackers++;
  2987.             }
  2988.         }
  2989.         i++;
  2990.     }
  2991.  
  2992.     i = 0;
  2993.  
  2994.     while (i < MAX_CLIENTS)
  2995.     {
  2996.         ent = &g_entities[i];
  2997.  
  2998.         if (ent && ent->client && botstates[i] && botstates[i]->squadLeader && botstates[i]->squadLeader->s.number == bs->client && i != bs->client)
  2999.         {
  3000.             squad[squadmates] = ent;
  3001.             squadmates++;
  3002.         }
  3003.  
  3004.         i++;
  3005.     }
  3006.  
  3007.     squad[squadmates] = &g_entities[bs->client];
  3008.     squadmates++;
  3009.  
  3010.     i = 0;
  3011.  
  3012.     if (enemyHasOurFlag && !weHaveEnemyFlag)
  3013.     { //start off with an attacker instead of a retriever if we don't have the enemy flag yet so that they can't capture it first.
  3014.       //after that we focus on getting our flag back.
  3015.         attackRetrievePriority = 1;
  3016.     }
  3017.  
  3018.     while (i < squadmates)
  3019.     {
  3020.         if (squad[i] && squad[i]->client && botstates[squad[i]->s.number])
  3021.         {
  3022.             if (botstates[squad[i]->s.number]->ctfState != CTFSTATE_GETFLAGHOME)
  3023.             { //never tell a bot to stop trying to bring the flag to the base
  3024.                 if (defendAttackPriority)
  3025.                 {
  3026.                     if (weHaveEnemyFlag)
  3027.                     {
  3028.                         if (guardDefendPriority)
  3029.                         {
  3030.                             botstates[squad[i]->s.number]->ctfState = CTFSTATE_GUARDCARRIER;
  3031.                             guardDefendPriority = 0;
  3032.                         }
  3033.                         else
  3034.                         {
  3035.                             botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER;
  3036.                             guardDefendPriority = 1;
  3037.                         }
  3038.                     }
  3039.                     else
  3040.                     {
  3041.                         botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER;
  3042.                     }
  3043.                     defendAttackPriority = 0;
  3044.                 }
  3045.                 else
  3046.                 {
  3047.                     if (enemyHasOurFlag)
  3048.                     {
  3049.                         if (attackRetrievePriority)
  3050.                         {
  3051.                             botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER;
  3052.                             attackRetrievePriority = 0;
  3053.                         }
  3054.                         else
  3055.                         {
  3056.                             botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL;
  3057.                             attackRetrievePriority = 1;
  3058.                         }
  3059.                     }
  3060.                     else
  3061.                     {
  3062.                         botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER;
  3063.                     }
  3064.                     defendAttackPriority = 1;
  3065.                 }
  3066.             }
  3067.             else if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag)
  3068.             { //I'm the only one on my team who will attack and the enemy has my flag, I have to go after him
  3069.                 botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL;
  3070.             }
  3071.         }
  3072.  
  3073.         i++;
  3074.     }
  3075. #endif
  3076. }
  3077.  
  3078. void BotDoTeamplayAI(bot_state_t *bs)
  3079. {
  3080.     if (bs->state_Forced)
  3081.     {
  3082.         bs->teamplayState = bs->state_Forced;
  3083.     }
  3084.  
  3085.     if (bs->teamplayState == TEAMPLAYSTATE_REGROUP)
  3086.     { //force to find a new leader
  3087.         bs->squadLeader = NULL;
  3088.         bs->isSquadLeader = 0;
  3089.     }
  3090. }
  3091.  
  3092. void CommanderBotTeamplayAI(bot_state_t *bs)
  3093. {
  3094.     int i = 0;
  3095.     int squadmates = 0;
  3096.     int teammates = 0;
  3097.     int teammate_indanger = -1;
  3098.     int teammate_helped = 0;
  3099.     int foundsquadleader = 0;
  3100.     int worsthealth = 50;
  3101.     gentity_t *squad[MAX_CLIENTS];
  3102.     gentity_t *ent;
  3103.     bot_state_t *bst;
  3104.  
  3105.     while (i < MAX_CLIENTS)
  3106.     {
  3107.         ent = &g_entities[i];
  3108.  
  3109.         if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number])
  3110.         {
  3111.             bst = botstates[ent->s.number];
  3112.  
  3113.             if (foundsquadleader && bst && bst->isSquadLeader)
  3114.             { //never more than one squad leader
  3115.                 bst->isSquadLeader = 0;
  3116.             }
  3117.  
  3118.             if (bst && !bst->isSquadLeader)
  3119.             {
  3120.                 squad[squadmates] = ent;
  3121.                 squadmates++;
  3122.             }
  3123.             else if (bst)
  3124.             {
  3125.                 foundsquadleader = 1;
  3126.             }
  3127.         }
  3128.  
  3129.         if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent))
  3130.         {
  3131.             teammates++;
  3132.  
  3133.             if (ent->health < worsthealth)
  3134.             {
  3135.                 teammate_indanger = ent->s.number;
  3136.                 worsthealth = ent->health;
  3137.             }
  3138.         }
  3139.  
  3140.         i++;
  3141.     }
  3142.     
  3143.     if (!squadmates)
  3144.     {
  3145.         return;
  3146.     }
  3147.  
  3148.     i = 0;
  3149.  
  3150.     while (i < squadmates && squad[i])
  3151.     {
  3152.         bst = botstates[squad[i]->s.number];
  3153.  
  3154.         if (bst && !bst->state_Forced)
  3155.         { //only order if this guy is not being ordered directly by the real player team leader
  3156.             if (teammate_indanger >= 0 && !teammate_helped)
  3157.             { //send someone out to help whoever needs help most at the moment
  3158.                 bst->teamplayState = TEAMPLAYSTATE_ASSISTING;
  3159.                 bst->squadLeader = &g_entities[teammate_indanger];
  3160.                 teammate_helped = 1;
  3161.             }
  3162.             else if ((teammate_indanger == -1 || teammate_helped) && bst->teamplayState == TEAMPLAYSTATE_ASSISTING)
  3163.             { //no teammates need help badly, but this guy is trying to help them anyway, so stop
  3164.                 bst->teamplayState = TEAMPLAYSTATE_FOLLOWING;
  3165.                 bst->squadLeader = &g_entities[bs->client];
  3166.             }
  3167.  
  3168.             if (bs->squadRegroupInterval < level.time && Q_irand(1, 10) < 5)
  3169.             { //every so often tell the squad to regroup for the sake of variation
  3170.                 if (bst->teamplayState == TEAMPLAYSTATE_FOLLOWING)
  3171.                 {
  3172.                     bst->teamplayState = TEAMPLAYSTATE_REGROUP;
  3173.                 }
  3174.  
  3175.                 bs->isSquadLeader = 0;
  3176.                 bs->squadCannotLead = level.time + 500;
  3177.                 bs->squadRegroupInterval = level.time + Q_irand(45000, 65000);
  3178.             }
  3179.         }
  3180.  
  3181.         i++;
  3182.     }    
  3183. }
  3184.  
  3185. void CommanderBotAI(bot_state_t *bs)
  3186. {
  3187. /*
  3188.     if (level.gametype == GT_CTF)
  3189.     {
  3190.         CommanderBotCTFAI(bs);
  3191.     }
  3192.     else if (level.gametype == GT_TDM)
  3193.     {
  3194.         CommanderBotTeamplayAI(bs);
  3195.     }
  3196. */
  3197. }
  3198.  
  3199.  
  3200. void MeleeCombatHandling(bot_state_t *bs)
  3201. {
  3202.     vec3_t usethisvec;
  3203.     vec3_t downvec;
  3204.     vec3_t midorg;
  3205.     vec3_t a;
  3206.     vec3_t fwd;
  3207.     vec3_t mins, maxs;
  3208.     trace_t tr;
  3209.     int en_down;
  3210.     int me_down;
  3211.     int mid_down;
  3212.  
  3213.     if (!bs->currentEnemy)
  3214.     {
  3215.         return;
  3216.     }
  3217.  
  3218.     if (bs->currentEnemy->client)
  3219.     {
  3220.         VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
  3221.     }
  3222.     else
  3223.     {
  3224.         VectorCopy(bs->currentEnemy->s.origin, usethisvec);
  3225.     }
  3226.  
  3227.     if (bs->meleeStrafeTime < level.time)
  3228.     {
  3229.         if (bs->meleeStrafeDir)
  3230.         {
  3231.             bs->meleeStrafeDir = 0;
  3232.         }
  3233.         else
  3234.         {
  3235.             bs->meleeStrafeDir = 1;
  3236.         }
  3237.  
  3238.         bs->meleeStrafeTime = level.time + Q_irand(500, 1800);
  3239.     }
  3240.  
  3241.     mins[0] = -15;
  3242.     mins[1] = -15;
  3243.     mins[2] = -24;
  3244.     maxs[0] = 15;
  3245.     maxs[1] = 15;
  3246.     maxs[2] = 32;
  3247.  
  3248.     VectorCopy(usethisvec, downvec);
  3249.     downvec[2] -= 4096;
  3250.  
  3251.     trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID);
  3252.  
  3253.     en_down = (int)tr.endpos[2];
  3254.  
  3255.     VectorCopy(bs->origin, downvec);
  3256.     downvec[2] -= 4096;
  3257.  
  3258.     trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID);
  3259.  
  3260.     me_down = (int)tr.endpos[2];
  3261.  
  3262.     VectorSubtract(usethisvec, bs->origin, a);
  3263.     vectoangles(a, a);
  3264.     AngleVectors(a, fwd, NULL, NULL);
  3265.  
  3266.     midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2;
  3267.     midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2;
  3268.     midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2;
  3269.  
  3270.     VectorCopy(midorg, downvec);
  3271.     downvec[2] -= 4096;
  3272.  
  3273.     trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID);
  3274.  
  3275.     mid_down = (int)tr.endpos[2];
  3276.  
  3277.     if (me_down == en_down &&
  3278.         en_down == mid_down)
  3279.     {
  3280.         VectorCopy(usethisvec, bs->goalPosition);
  3281.     }
  3282. }
  3283.  
  3284. #if 0
  3285. void SaberCombatHandling(bot_state_t *bs)
  3286. {
  3287.     vec3_t usethisvec;
  3288.     vec3_t downvec;
  3289.     vec3_t midorg;
  3290.     vec3_t a;
  3291.     vec3_t fwd;
  3292.     vec3_t mins, maxs;
  3293.     trace_t tr;
  3294.     int en_down;
  3295.     int me_down;
  3296.     int mid_down;
  3297.  
  3298.     if (!bs->currentEnemy)
  3299.     {
  3300.         return;
  3301.     }
  3302.  
  3303.     if (bs->currentEnemy->client)
  3304.     {
  3305.         VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
  3306.     }
  3307.     else
  3308.     {
  3309.         VectorCopy(bs->currentEnemy->s.origin, usethisvec);
  3310.     }
  3311.  
  3312.     if (bs->meleeStrafeTime < level.time)
  3313.     {
  3314.         if (bs->meleeStrafeDir)
  3315.         {
  3316.             bs->meleeStrafeDir = 0;
  3317.         }
  3318.         else
  3319.         {
  3320.             bs->meleeStrafeDir = 1;
  3321.         }
  3322.  
  3323.         bs->meleeStrafeTime = level.time + Q_irand(500, 1800);
  3324.     }
  3325.  
  3326.     mins[0] = -15;
  3327.     mins[1] = -15;
  3328.     mins[2] = -24;
  3329.     maxs[0] = 15;
  3330.     maxs[1] = 15;
  3331.     maxs[2] = 32;
  3332.  
  3333.     VectorCopy(usethisvec, downvec);
  3334.     downvec[2] -= 4096;
  3335.  
  3336.     trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID);
  3337.  
  3338.     en_down = (int)tr.endpos[2];
  3339.  
  3340.     if (tr.startsolid || tr.allsolid)
  3341.     {
  3342.         en_down = 1;
  3343.         me_down = 2;
  3344.     }
  3345.     else
  3346.     {
  3347.         VectorCopy(bs->origin, downvec);
  3348.         downvec[2] -= 4096;
  3349.  
  3350.         trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID);
  3351.  
  3352.         me_down = (int)tr.endpos[2];
  3353.  
  3354.         if (tr.startsolid || tr.allsolid)
  3355.         {
  3356.             en_down = 1;
  3357.             me_down = 2;
  3358.         }
  3359.     }
  3360.  
  3361.     VectorSubtract(usethisvec, bs->origin, a);
  3362.     vectoangles(a, a);
  3363.     AngleVectors(a, fwd, NULL, NULL);
  3364.  
  3365.     midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2;
  3366.     midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2;
  3367.     midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2;
  3368.  
  3369.     VectorCopy(midorg, downvec);
  3370.     downvec[2] -= 4096;
  3371.  
  3372.     trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID);
  3373.  
  3374.     mid_down = (int)tr.endpos[2];
  3375.  
  3376.     if (me_down == en_down &&
  3377.         en_down == mid_down)
  3378.     {
  3379.         if (usethisvec[2] > (bs->origin[2]+32) &&
  3380.             bs->currentEnemy->client &&
  3381.             bs->currentEnemy->client->ps.groundEntityNum == ENTITYNUM_NONE)
  3382.         {
  3383.             bs->jumpTime = level.time + 100;
  3384.         }
  3385.  
  3386.         if (bs->frame_Enemy_Len > 128)
  3387.         { //be ready to attack
  3388.             bs->saberDefending = 0;
  3389.             bs->saberDefendDecideTime = level.time + Q_irand(1000, 2000);
  3390.         }
  3391.         else
  3392.         {
  3393.             if (bs->saberDefendDecideTime < level.time)
  3394.             {
  3395.                 if (bs->saberDefending)
  3396.                 {
  3397.                     bs->saberDefending = 0;
  3398.                 }
  3399.                 else
  3400.                 {
  3401.                     bs->saberDefending = 1;
  3402.                 }
  3403.  
  3404.                 bs->saberDefendDecideTime = level.time + Q_irand(500, 2000);
  3405.             }
  3406.         }
  3407.  
  3408.         if (bs->frame_Enemy_Len < 54)
  3409.         {
  3410.             VectorCopy(bs->origin, bs->goalPosition);
  3411.             bs->saberBFTime = 0;
  3412.         }
  3413.         else
  3414.         {
  3415.             VectorCopy(usethisvec, bs->goalPosition);
  3416.         }
  3417.  
  3418.         if (bs->frame_Enemy_Len > 90 && bs->saberBFTime > level.time && bs->saberBTime > level.time && bs->beStill < level.time && bs->saberSTime < level.time)
  3419.         {
  3420.             bs->beStill = level.time + Q_irand(500, 1000);
  3421.             bs->saberSTime = level.time + Q_irand(1200, 1800);
  3422.         }
  3423.         else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len < 80 && (Q_irand(1, 10) < 8 && bs->saberBFTime < level.time) || bs->saberBTime > level.time)
  3424.         {
  3425.             vec3_t vs;
  3426.             vec3_t groundcheck;
  3427.  
  3428.             VectorSubtract(bs->origin, usethisvec, vs);
  3429.             VectorNormalize(vs);
  3430.  
  3431.             bs->goalPosition[0] = bs->origin[0] + vs[0]*64;
  3432.             bs->goalPosition[1] = bs->origin[1] + vs[1]*64;
  3433.             bs->goalPosition[2] = bs->origin[2] + vs[2]*64;
  3434.  
  3435.             if (bs->saberBTime < level.time)
  3436.             {
  3437.                 bs->saberBFTime = level.time + Q_irand(900, 1300);
  3438.                 bs->saberBTime = level.time + Q_irand(300, 700);
  3439.             }
  3440.  
  3441.             VectorCopy(bs->goalPosition, groundcheck);
  3442.  
  3443.             groundcheck[2] -= 64;
  3444.  
  3445.             trap_Trace(&tr, bs->goalPosition, NULL, NULL, groundcheck, bs->client, MASK_SOLID);
  3446.             
  3447.             if (tr.fraction == 1.0)
  3448.             { //don't back off of a ledge
  3449.                 VectorCopy(usethisvec, bs->goalPosition);
  3450.             }
  3451.         }
  3452.         else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len >= 75)
  3453.         {
  3454.             bs->saberBFTime = level.time + Q_irand(700, 1300);
  3455.             bs->saberBTime = 0;
  3456.         }
  3457.  
  3458.         /*AngleVectors(bs->viewangles, NULL, fwd, NULL);
  3459.  
  3460.         if (bs->meleeStrafeDir)
  3461.         {
  3462.             bs->goalPosition[0] += fwd[0]*16;
  3463.             bs->goalPosition[1] += fwd[1]*16;
  3464.             bs->goalPosition[2] += fwd[2]*16;
  3465.         }
  3466.         else
  3467.         {
  3468.             bs->goalPosition[0] -= fwd[0]*16;
  3469.             bs->goalPosition[1] -= fwd[1]*16;
  3470.             bs->goalPosition[2] -= fwd[2]*16;
  3471.         }*/
  3472.     }
  3473.     else if (bs->frame_Enemy_Len <= 56)
  3474.     {
  3475.         bs->doAttack = 1;
  3476.         bs->saberDefending = 0;
  3477.     }
  3478. }
  3479. #endif
  3480.  
  3481. float BotWeaponCanLead(bot_state_t *bs)
  3482. {
  3483.     switch (bs->cur_ps.weapon)
  3484.     {
  3485.     default:
  3486.         return 0;
  3487.     case WP_KNIFE:
  3488.         return 0.5;
  3489.  
  3490.     // no leading needed for any bullet weapons
  3491.     case WP_M1911A1_PISTOL:          
  3492.     case WP_USSOCOM_PISTOL:         
  3493.     case WP_M4_ASSAULT_RIFLE:        
  3494.     case WP_AK74_ASSAULT_RIFLE:      
  3495.     case WP_M60_MACHINEGUN:          
  3496.     case WP_MICRO_UZI_SUBMACHINEGUN: 
  3497.     case WP_M3A1_SUBMACHINEGUN:      
  3498.     case WP_MP5:
  3499.     case WP_MSG90A1:    
  3500.     case WP_USAS_12_SHOTGUN:         
  3501.     case WP_M590_SHOTGUN:            
  3502.         return 0;
  3503.     // projectile weapons lead
  3504.     case WP_MM1_GRENADE_LAUNCHER:    
  3505.         return 0.5;
  3506.     case WP_RPG7_LAUNCHER:           
  3507.         return 0.5;
  3508.     case WP_M84_GRENADE:             
  3509.     case WP_SMOHG92_GRENADE:         
  3510.     case WP_ANM14_GRENADE:           
  3511.     case WP_M15_GRENADE:             
  3512.         return 0.7;
  3513.     }
  3514. }
  3515.  
  3516. //    Calculate proper angle for ballistic weapon
  3517. static float CalcWeaponAngle(float vel, float gravity, float targetRange)
  3518. {
  3519.     float angle = 0;
  3520.     float val =  (gravity * targetRange) / (vel * vel);
  3521.  
  3522.     if (val >= -1 && val <= 1)
  3523.         angle = RAD2DEG(asin(val)/2);
  3524.     else
  3525.         angle = 90;
  3526.  
  3527.     return angle;
  3528. }
  3529.  
  3530. void BotAimLeading(bot_state_t *bs, vec3_t headlevel, float leadAmount)
  3531. {
  3532.     int x;
  3533.     vec3_t predictedSpot;
  3534.     vec3_t movementVector;
  3535.     vec3_t a, ang;
  3536.     float vtotal;
  3537.  
  3538.     if (!bs->currentEnemy ||
  3539.         !bs->currentEnemy->client)
  3540.     {
  3541.         return;
  3542.     }
  3543.  
  3544.     if (!bs->frame_Enemy_Len)
  3545.     {
  3546.         return;
  3547.     }
  3548.  
  3549.     vtotal = 0;
  3550.  
  3551.     if (bs->currentEnemy->client->ps.velocity[0] < 0)
  3552.     {
  3553.         vtotal += -bs->currentEnemy->client->ps.velocity[0];
  3554.     }
  3555.     else
  3556.     {
  3557.         vtotal += bs->currentEnemy->client->ps.velocity[0];
  3558.     }
  3559.  
  3560.     if (bs->currentEnemy->client->ps.velocity[1] < 0)
  3561.     {
  3562.         vtotal += -bs->currentEnemy->client->ps.velocity[1];
  3563.     }
  3564.     else
  3565.     {
  3566.         vtotal += bs->currentEnemy->client->ps.velocity[1];
  3567.     }
  3568.  
  3569.     if (bs->currentEnemy->client->ps.velocity[2] < 0)
  3570.     {
  3571.         vtotal += -bs->currentEnemy->client->ps.velocity[2];
  3572.     }
  3573.     else
  3574.     {
  3575.         vtotal += bs->currentEnemy->client->ps.velocity[2];
  3576.     }
  3577.  
  3578.     //G_Printf("Leadin target with a velocity total of %f\n", vtotal);
  3579.  
  3580.     VectorCopy(bs->currentEnemy->client->ps.velocity, movementVector);
  3581.  
  3582.     VectorNormalize(movementVector);
  3583.  
  3584.     x = bs->frame_Enemy_Len*leadAmount; //hardly calculated with an exact science, but it works
  3585.  
  3586.     if (vtotal > 400)
  3587.     {
  3588.         vtotal = 400;
  3589.     }
  3590.  
  3591.     if (vtotal)
  3592.     {
  3593.         x = (bs->frame_Enemy_Len*0.9)*leadAmount*(vtotal*0.0012); //hardly calculated with an exact science, but it works
  3594.     }
  3595.     else
  3596.     {
  3597.         x = (bs->frame_Enemy_Len*0.9)*leadAmount; //hardly calculated with an exact science, but it works
  3598.     }
  3599.  
  3600.     predictedSpot[0] = headlevel[0] + (movementVector[0]*x);
  3601.     predictedSpot[1] = headlevel[1] + (movementVector[1]*x);
  3602.     predictedSpot[2] = headlevel[2] + (movementVector[2]*x);
  3603.  
  3604.     VectorSubtract(predictedSpot, bs->eye, a);
  3605.     vectoangles(a, ang);
  3606.     VectorCopy(ang, bs->goalAngles);
  3607. }
  3608.  
  3609. void BotAimOffsetGoalAngles(bot_state_t *bs)
  3610. {
  3611.     int i;
  3612.     float accVal;
  3613.     i = 0;
  3614.  
  3615.     if (bs->skills.perfectaim)
  3616.     {
  3617.         return;
  3618.     }
  3619.  
  3620.     if (bs->aimOffsetTime > level.time)
  3621.     {
  3622.         if (bs->aimOffsetAmtYaw)
  3623.         {
  3624.             bs->goalAngles[YAW] += bs->aimOffsetAmtYaw;
  3625.         }
  3626.  
  3627.         if (bs->aimOffsetAmtPitch)
  3628.         {
  3629.             bs->goalAngles[PITCH] += bs->aimOffsetAmtPitch;
  3630.         }
  3631.         
  3632.         while (i <= 2)
  3633.         {
  3634.             if (bs->goalAngles[i] > 360)
  3635.             {
  3636.                 bs->goalAngles[i] -= 360;
  3637.             }
  3638.  
  3639.             if (bs->goalAngles[i] < 0)
  3640.             {
  3641.                 bs->goalAngles[i] += 360;
  3642.             }
  3643.  
  3644.             i++;
  3645.         }
  3646.         return;
  3647.     }
  3648.  
  3649.     accVal = bs->skills.accuracy/bs->settings.skill;
  3650.  
  3651.     if (bs->revengeEnemy && bs->revengeHateLevel &&
  3652.         bs->currentEnemy == bs->revengeEnemy)
  3653.     { //bot becomes more skilled as anger level raises
  3654.         accVal = accVal/bs->revengeHateLevel;
  3655.     }
  3656.  
  3657.     if (bs->currentEnemy && bs->frame_Enemy_Vis)
  3658.     { //assume our goal is aiming at the enemy, seeing as he's visible and all
  3659.         if (!bs->currentEnemy->s.pos.trDelta[0] &&
  3660.             !bs->currentEnemy->s.pos.trDelta[1] &&
  3661.             !bs->currentEnemy->s.pos.trDelta[2])
  3662.         {
  3663.             accVal = 0; //he's not even moving, so he shouldn't really be hard to hit.
  3664.         }
  3665.         else
  3666.         {
  3667.             accVal += accVal*0.25; //if he's moving he's this much harder to hit
  3668.         }
  3669.  
  3670.         if (g_entities[bs->client].s.pos.trDelta[0] ||
  3671.             g_entities[bs->client].s.pos.trDelta[1] ||
  3672.             g_entities[bs->client].s.pos.trDelta[2])
  3673.         {
  3674.             accVal += accVal*0.15; //make it somewhat harder to aim if we're moving also
  3675.         }
  3676.     }
  3677.  
  3678.     if (accVal > 90)
  3679.     {
  3680.         accVal = 90;
  3681.     }
  3682.     if (accVal < 1)
  3683.     {
  3684.         accVal = 0;
  3685.     }
  3686.  
  3687.     if (!accVal)
  3688.     {
  3689.         bs->aimOffsetAmtYaw = 0;
  3690.         bs->aimOffsetAmtPitch = 0;
  3691.         return;
  3692.     }
  3693.  
  3694.     if (rand()%10 <= 5)
  3695.     {
  3696.         bs->aimOffsetAmtYaw = rand()%(int)accVal;
  3697.     }
  3698.     else
  3699.     {
  3700.         bs->aimOffsetAmtYaw = -(rand()%(int)accVal);
  3701.     }
  3702.  
  3703.     if (rand()%10 <= 5)
  3704.     {
  3705.         bs->aimOffsetAmtPitch = rand()%(int)accVal;
  3706.     }
  3707.     else
  3708.     {
  3709.         bs->aimOffsetAmtPitch = -(rand()%(int)accVal);
  3710.     }
  3711.  
  3712.     bs->aimOffsetTime = level.time + rand()%500 + 200;
  3713. }
  3714.  
  3715. int ShouldSecondaryFire(bot_state_t *bs, vec3_t eorg, vec3_t dir)
  3716. {
  3717.     weaponData_t *weapon = &weaponData[bs->cur_ps.weapon];
  3718.     attackData_t *attack = &weapon->attack[ATTACK_ALTERNATE];
  3719.  
  3720.     if ((bs->cur_ps.ammo[attack->ammoIndex] < 1) || 0 == attack->damage)
  3721.     {    // no ammo for alt fire
  3722.         return 0;
  3723.     }
  3724.  
  3725. #ifdef BOT_LAUNCH_ANGLES
  3726.     if ((attack->weaponFlags & PROJECTILE_FIRE) && bs->frame_Enemy_Len > MAX_PROJECTILE_DISTANCE) //don't forget to make sure the bot doesn't shoot it off in his own face!
  3727.     {    
  3728.         // if alt-fire is projectile
  3729.         if (bs->frame_Enemy_Len < attack->rV.velocity * attack->projectileLifetime * 0.001)
  3730.         {    
  3731.             // in range
  3732.             // 2D range
  3733.             float range2D = max(0, SQRTFAST(dir[0]*dir[0] + dir[1]*dir[1]) - attack->splashRadius); 
  3734.             if (attack->weaponFlags & PROJECTILE_TIMED)
  3735.             {    // if timed projectile, let bounce for 1 sec.
  3736.                 range2D = max(0, range2D - attack->rV.velocity);
  3737.             }
  3738.             // calculate projectile launch angle
  3739.             bs->launchAngle = CalcWeaponAngle( attack->rV.velocity, DEFAULT_GRAVITY, range2D);
  3740.             if (bs->launchAngle < 90)
  3741.             {
  3742.                 if (!OrgVisible(bs->eye, eorg, bs->client))
  3743.                     bs->launchAngle = 90 - bs->launchAngle;
  3744.                 return 1;
  3745.             }
  3746.         }
  3747.     }
  3748.     else if (bs->frame_Enemy_Len < attack->rV.range)
  3749.     {    // alt-fire is bullet
  3750.         return 1;
  3751.     }
  3752. #else
  3753.     if ((attack->weaponFlags & PROJECTILE_FIRE) && bs->frame_Enemy_Len > MAX_PROJECTILE_DISTANCE) //don't forget to make sure the bot doesn't shoot it off in his own face!
  3754.     {
  3755.         return 1;
  3756.     }
  3757.     else if (bs->frame_Enemy_Len < attack->rV.range)
  3758.     {    // alt-fire is bullet
  3759.         return 1;
  3760.     }
  3761. #endif
  3762.     return 0;
  3763. }
  3764.  
  3765. int CombatBotAI(bot_state_t *bs, float thinktime)
  3766. {
  3767.     vec3_t eorg, a, dir;
  3768.     int secFire;
  3769.     float fovcheck;
  3770.  
  3771.     if (!bs->currentEnemy)
  3772.     {
  3773.         return 0;
  3774.     }
  3775.  
  3776.     if (bs->currentEnemy->client)
  3777.     {
  3778.         VectorCopy(bs->currentEnemy->client->ps.origin, eorg);
  3779.     }
  3780.     else
  3781.     {
  3782.         VectorCopy(bs->currentEnemy->s.origin, eorg);
  3783.     }
  3784.  
  3785.     VectorSubtract(eorg, bs->eye, dir);
  3786.     vectoangles(dir, a);
  3787.  
  3788.     if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER)
  3789.     {
  3790.         if (bs->frame_Enemy_Len <= SABER_ATTACK_RANGE)
  3791.         {
  3792.             bs->doAttack = 1;
  3793.         }
  3794.     }
  3795.     else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)
  3796.     {
  3797.         if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE)
  3798.         {
  3799.             bs->doAttack = 1;
  3800.         }
  3801.     }
  3802.     else
  3803.     {
  3804.         if (bs->cur_ps.weapon == WP_MSG90A1 || bs->cur_ps.weapon == WP_RPG7_LAUNCHER)
  3805.         { //be careful with the hurty weapons
  3806.             fovcheck = 10;
  3807.         }
  3808.         else
  3809.         {
  3810.             fovcheck = 60;
  3811.         }
  3812.  
  3813.         if (bs->frame_Enemy_Len < 128)
  3814.         {
  3815.             fovcheck *= 2;
  3816.         }
  3817.  
  3818.         if (InFieldOfVision(bs->viewangles, fovcheck, a))
  3819.         {
  3820. #ifdef BOT_LAUNCH_ANGLES
  3821.             weaponData_t *weapon = &weaponData[bs->cur_ps.weapon];
  3822.             if (CAT_GRENADE == weapon->category || WP_MM1_GRENADE_LAUNCHER == bs->cur_ps.weapon)
  3823.             {    
  3824.                 attackData_t* attack;
  3825.                 float          range2D;
  3826.  
  3827.                 // are we using a grenade type weapon?
  3828.                 if (bs->frame_Enemy_Len < weapon->attack[ATTACK_NORMAL].rV.velocity * weapon->attack[ATTACK_NORMAL].projectileLifetime * 0.001)
  3829.                 {
  3830.                     attack = &weapon->attack[ATTACK_NORMAL];
  3831.                 }
  3832.                 else
  3833.                 {
  3834.                     attack = &weapon->attack[ATTACK_ALTERNATE];
  3835.                 }
  3836.  
  3837.                 // only 2D range since Z is for the ballistic path only
  3838.                 range2D = max(0, SQRTFAST(dir[0]*dir[0] + dir[1]*dir[1]) - attack->splashRadius); 
  3839.                 if (attack->weaponFlags & PROJECTILE_TIMED)
  3840.                 {    
  3841.                     // if timed projectile, let bounce for 1 sec.
  3842.                     range2D = max(0, range2D - attack->rV.velocity);
  3843.                 }
  3844.                 
  3845.                 bs->launchAngle = CalcWeaponAngle(attack->rV.velocity, DEFAULT_GRAVITY, range2D);
  3846.                 if (bs->launchAngle < 90)
  3847.                 {
  3848.                     if (!OrgVisible(bs->eye, eorg, bs->client))
  3849.                     {
  3850.                         bs->launchAngle = 90 - bs->launchAngle;
  3851.                     }
  3852.  
  3853.                     bs->doAttack = 1;
  3854.                 }
  3855.             }
  3856.             else
  3857. #endif
  3858.             {
  3859.                 secFire = ShouldSecondaryFire(bs, eorg, dir);
  3860.  
  3861.                 if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT)
  3862.                 {
  3863.                     bs->altChargeTime = Q_irand(500, 1000);
  3864.                 }
  3865.  
  3866.                 if (secFire == 1)
  3867.                 {
  3868.                     bs->doAltAttack = 1;
  3869.                 }
  3870.                 else if (!secFire)
  3871.                 {
  3872.                     bs->doAttack = 1;
  3873.                 }
  3874.  
  3875.                 if (secFire == 2)
  3876.                 { //released a charge
  3877.                     return 1;
  3878.                 }
  3879.             }
  3880.         }
  3881.     }
  3882.  
  3883.     return 0;
  3884. }
  3885.  
  3886. int BotFallbackNavigation(bot_state_t *bs)
  3887. {
  3888.     vec3_t b_angle, fwd, trto, mins, maxs;
  3889.     trace_t tr;
  3890.  
  3891.     if (bs->currentEnemy && bs->frame_Enemy_Vis)
  3892.     {
  3893.         return 2; //we're busy
  3894.     }
  3895.  
  3896.     mins[0] = -15;
  3897.     mins[1] = -15;
  3898.     mins[2] = 0;
  3899.     maxs[0] = 15;
  3900.     maxs[1] = 15;
  3901.     maxs[2] = 32;
  3902.  
  3903.     bs->goalAngles[PITCH] = 0;
  3904.     bs->goalAngles[ROLL] = 0;
  3905.  
  3906.     VectorCopy(bs->goalAngles, b_angle);
  3907.  
  3908.     AngleVectors(b_angle, fwd, NULL, NULL);
  3909.  
  3910.     trto[0] = bs->origin[0] + fwd[0]*16;
  3911.     trto[1] = bs->origin[1] + fwd[1]*16;
  3912.     trto[2] = bs->origin[2] + fwd[2]*16;
  3913.  
  3914.     trap_Trace(&tr, bs->origin, mins, maxs, trto, -1, MASK_SOLID);
  3915.  
  3916.     if (tr.fraction == 1)
  3917.     {
  3918.         VectorCopy(trto, bs->goalPosition);
  3919.         return 1; //success!
  3920.     }
  3921.     else
  3922.     {
  3923.         bs->goalAngles[YAW] = rand()%360;
  3924.     }
  3925.  
  3926.     return 0;
  3927. }
  3928.  
  3929. int BotTryAnotherWeapon(bot_state_t *bs)
  3930. { //out of ammo, resort to the first weapon we come across that has ammo
  3931.     int i;
  3932.  
  3933.     i = 0;
  3934.  
  3935.     while (i < WP_NUM_WEAPONS)
  3936.     {
  3937.         if ((bs->cur_ps.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex] > 1 || bs->cur_ps.clip[ATTACK_NORMAL][i] > 1) && 
  3938.             (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
  3939.         {
  3940.             bs->virtualWeapon = i;
  3941.             trap_EA_SelectWeapon(bs->client, i);
  3942.             //bs->cur_ps.weapon = i;
  3943.             //level.clients[bs->client].ps.weapon = i;
  3944.             return 1;
  3945.         }
  3946.  
  3947.         i++;
  3948.     }
  3949.  
  3950.     if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1)
  3951.     { //should always have this.. shouldn't we?
  3952.         bs->virtualWeapon = 1;
  3953.         trap_EA_SelectWeapon(bs->client, 1);
  3954.         //bs->cur_ps.weapon = 1;
  3955.         //level.clients[bs->client].ps.weapon = 1;
  3956.         return 1;
  3957.     }
  3958.  
  3959.     return 0;
  3960. }
  3961.  
  3962. int BotSelectIdealWeapon(bot_state_t *bs)
  3963. {
  3964.     int i;
  3965.     int bestweight = -1;
  3966.     int bestweapon = 0;
  3967.  
  3968.     i = 0;
  3969.  
  3970.     while (i < WP_NUM_WEAPONS)
  3971.     {
  3972.         if ((bs->cur_ps.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex] > 1 || bs->cur_ps.clip[ATTACK_NORMAL][i] > 1) && 
  3973.             (bs->botWeaponWeights[i] > bestweight) &&
  3974.             (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
  3975.         {
  3976.             bestweight = bs->botWeaponWeights[i];
  3977.             bestweapon = i;
  3978.         }
  3979.  
  3980.         i++;
  3981.     }
  3982.  
  3983.     if (bestweight != -1 && bs->cur_ps.weapon != bestweapon && bs->virtualWeapon != bestweapon)
  3984.     {
  3985.         bs->virtualWeapon = bestweapon;
  3986.         trap_EA_SelectWeapon(bs->client, bestweapon);
  3987.         //bs->cur_ps.weapon = bestweapon;
  3988.         //level.clients[bs->client].ps.weapon = bestweapon;
  3989.         return 1;
  3990.     }
  3991.  
  3992.     return 0;
  3993. }
  3994.  
  3995. int BotSelectChoiceWeapon(bot_state_t *bs, int weapon, int doselection)
  3996. { //if !doselection then bot will only check if he has the specified weapon and return 1 (yes) or 0 (no)
  3997.     int i;
  3998.     int hasit = 0;
  3999.  
  4000.     i = 0;
  4001.  
  4002.     while (i < WP_NUM_WEAPONS)
  4003.     {
  4004.         if ((bs->cur_ps.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex] > 1 || bs->cur_ps.clip[ATTACK_NORMAL][i] > 1) &&
  4005.             i == weapon &&
  4006.             (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
  4007.         {
  4008.             hasit = 1;
  4009.             break;
  4010.         }
  4011.  
  4012.         i++;
  4013.     }
  4014.  
  4015.     if (hasit && bs->cur_ps.weapon != weapon && doselection && bs->virtualWeapon != weapon)
  4016.     {
  4017.         bs->virtualWeapon = weapon;
  4018.         trap_EA_SelectWeapon(bs->client, weapon);
  4019.         //bs->cur_ps.weapon = weapon;
  4020.         //level.clients[bs->client].ps.weapon = weapon;
  4021.         return 2;
  4022.     }
  4023.  
  4024.     if (hasit)
  4025.     {
  4026.         return 1;
  4027.     }
  4028.  
  4029.     return 0;
  4030. }
  4031.  
  4032. int BotSelectMelee(bot_state_t *bs)
  4033. {
  4034.     if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1)
  4035.     {
  4036.         bs->virtualWeapon = 1;
  4037.         trap_EA_SelectWeapon(bs->client, 1);
  4038.         //bs->cur_ps.weapon = 1;
  4039.         //level.clients[bs->client].ps.weapon = 1;
  4040.         return 1;
  4041.     }
  4042.  
  4043.     return 0;
  4044. }
  4045.  
  4046. int GetLoveLevel(bot_state_t *bs, bot_state_t *love)
  4047. {
  4048.     int i = 0;
  4049.     const char *lname = NULL;
  4050.  
  4051.     if (!bs || !love || !g_entities[love->client].client)
  4052.     {
  4053.         return 0;
  4054.     }
  4055.  
  4056.     if (!bs->lovednum)
  4057.     {
  4058.         return 0;
  4059.     }
  4060.  
  4061.     trap_Cvar_Update(&bot_attachments);
  4062.  
  4063.     if (!bot_attachments.integer)
  4064.     {
  4065.         return 0;
  4066.     }
  4067.  
  4068.     lname = g_entities[love->client].client->pers.netname;
  4069.  
  4070.     if (!lname)
  4071.     {
  4072.         return 0;
  4073.     }
  4074.  
  4075.     while (i < bs->lovednum)
  4076.     {
  4077.         if (strcmp(bs->loved[i].name, lname) == 0)
  4078.         {
  4079.             return bs->loved[i].level;
  4080.         }
  4081.  
  4082.         i++;
  4083.     }
  4084.  
  4085.     return 0;
  4086. }
  4087.  
  4088. void BotLovedOneDied(bot_state_t *bs, bot_state_t *loved, int lovelevel)
  4089. {
  4090.     if (!loved->lastHurt || !loved->lastHurt->client ||
  4091.         loved->lastHurt->s.number == loved->client)
  4092.     {
  4093.         return;
  4094.     }
  4095.  
  4096.     if (!IsTeamplay())
  4097.     {
  4098.         if (lovelevel < 2)
  4099.         {
  4100.             return;
  4101.         }
  4102.     }
  4103.     else if (OnSameTeam(&g_entities[bs->client], loved->lastHurt))
  4104.     { //don't hate teammates no matter what
  4105.         return;
  4106.     }
  4107.  
  4108.     if (loved->client == loved->lastHurt->s.number)
  4109.     {
  4110.         return;
  4111.     }
  4112.  
  4113.     if (bs->client == loved->lastHurt->s.number)
  4114.     { //oops!
  4115.         return;
  4116.     }
  4117.     
  4118.     trap_Cvar_Update(&bot_attachments);
  4119.  
  4120.     if (!bot_attachments.integer)
  4121.     {
  4122.         return;
  4123.     }
  4124.  
  4125.     if (!PassLovedOneCheck(bs, loved->lastHurt))
  4126.     { //a loved one killed a loved one.. you cannot hate them
  4127.         bs->chatObject = loved->lastHurt;
  4128.         bs->chatAltObject = &g_entities[loved->client];
  4129.         BotDoChat(bs, "LovedOneKilledLovedOne", 0);
  4130.         return;
  4131.     }
  4132.  
  4133.     if (bs->revengeEnemy == loved->lastHurt)
  4134.     {
  4135.         if (bs->revengeHateLevel < bs->loved_death_thresh)
  4136.         {
  4137.             bs->revengeHateLevel++;
  4138.  
  4139.             if (bs->revengeHateLevel == bs->loved_death_thresh)
  4140.             {
  4141.                 //broke into the highest anger level
  4142.                 //CHAT: Hatred section
  4143.                 bs->chatObject = loved->lastHurt;
  4144.                 bs->chatAltObject = NULL;
  4145.                 BotDoChat(bs, "Hatred", 1);
  4146.             }
  4147.         }
  4148.     }
  4149.     else if (bs->revengeHateLevel < bs->loved_death_thresh-1)
  4150.     { //only switch hatred if we don't hate the existing revenge-enemy too much
  4151.         //CHAT: BelovedKilled section
  4152.         bs->chatObject = &g_entities[loved->client];
  4153.         bs->chatAltObject = loved->lastHurt;
  4154.         BotDoChat(bs, "BelovedKilled", 0);
  4155.         bs->revengeHateLevel = 0;
  4156.         bs->revengeEnemy = loved->lastHurt;
  4157.     }
  4158. }
  4159.  
  4160. void BotDeathNotify(bot_state_t *bs)
  4161. { //in case someone has an emotional attachment to us, we'll notify them
  4162.     int i = 0;
  4163.     int ltest = 0;
  4164.  
  4165.     while (i < MAX_CLIENTS)
  4166.     {
  4167.         if (botstates[i] && botstates[i]->lovednum)
  4168.         {
  4169.             ltest = 0;
  4170.             while (ltest < botstates[i]->lovednum)
  4171.             {
  4172.                 if (strcmp(level.clients[bs->client].pers.netname, botstates[i]->loved[ltest].name) == 0)
  4173.                 {
  4174.                     BotLovedOneDied(botstates[i], bs, botstates[i]->loved[ltest].level);
  4175.                     break;
  4176.                 }
  4177.  
  4178.                 ltest++;
  4179.             }
  4180.         }
  4181.  
  4182.         i++;
  4183.     }
  4184. }
  4185.  
  4186. void StrafeTracing(bot_state_t *bs)
  4187. {
  4188.     vec3_t mins, maxs;
  4189.     vec3_t right, rorg, drorg;
  4190.     trace_t tr;
  4191.  
  4192.     mins[0] = -15;
  4193.     mins[1] = -15;
  4194.     //mins[2] = -24;
  4195.     mins[2] = -22;
  4196.     maxs[0] = 15;
  4197.     maxs[1] = 15;
  4198.     maxs[2] = 32;
  4199.  
  4200.     AngleVectors(bs->viewangles, NULL, right, NULL);
  4201.  
  4202.     if (bs->meleeStrafeDir)
  4203.     {
  4204.         rorg[0] = bs->origin[0] - right[0]*32;
  4205.         rorg[1] = bs->origin[1] - right[1]*32;
  4206.         rorg[2] = bs->origin[2] - right[2]*32;
  4207.     }
  4208.     else
  4209.     {
  4210.         rorg[0] = bs->origin[0] + right[0]*32;
  4211.         rorg[1] = bs->origin[1] + right[1]*32;
  4212.         rorg[2] = bs->origin[2] + right[2]*32;
  4213.     }
  4214.  
  4215.     trap_Trace(&tr, bs->origin, mins, maxs, rorg, bs->client, MASK_SOLID);
  4216.  
  4217.     if (tr.fraction != 1)
  4218.     {
  4219.         bs->meleeStrafeDisable = level.time + Q_irand(500, 1500);
  4220.     }
  4221.  
  4222.     VectorCopy(rorg, drorg);
  4223.  
  4224.     drorg[2] -= 32;
  4225.  
  4226.     trap_Trace(&tr, rorg, NULL, NULL, drorg, bs->client, MASK_SOLID);
  4227.  
  4228.     if (tr.fraction == 1)
  4229.     { //this may be a dangerous ledge, so don't strafe over it just in case
  4230.         bs->meleeStrafeDisable = level.time + Q_irand(500, 1500);
  4231.     }
  4232. }
  4233.  
  4234. int PrimFiring(bot_state_t *bs)
  4235. {
  4236.     if (bs->cur_ps.weaponstate != WEAPON_CHARGING &&
  4237.         bs->doAttack)
  4238.     {
  4239.         return 1;
  4240.     }
  4241.  
  4242.     if (bs->cur_ps.weaponstate == WEAPON_CHARGING &&
  4243.         !bs->doAttack)
  4244.     {
  4245.         return 1;
  4246.     }
  4247.  
  4248.     return 0;
  4249. }
  4250.  
  4251. int KeepPrimFromFiring(bot_state_t *bs)
  4252. {
  4253.     if (bs->cur_ps.weaponstate != WEAPON_CHARGING &&
  4254.         bs->doAttack)
  4255.     {
  4256.         bs->doAttack = 0;
  4257.     }
  4258.  
  4259.     if (bs->cur_ps.weaponstate == WEAPON_CHARGING &&
  4260.         !bs->doAttack)
  4261.     {
  4262.         bs->doAttack = 1;
  4263.     }
  4264.  
  4265.     return 0;
  4266. }
  4267.  
  4268. int AltFiring(bot_state_t *bs)
  4269. {
  4270.     if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT &&
  4271.         bs->doAltAttack)
  4272.     {
  4273.         return 1;
  4274.     }
  4275.  
  4276.     if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
  4277.         !bs->doAltAttack)
  4278.     {
  4279.         return 1;
  4280.     }
  4281.  
  4282.     return 0;
  4283. }
  4284.  
  4285. int KeepAltFromFiring(bot_state_t *bs)
  4286. {
  4287.     if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT &&
  4288.         bs->doAltAttack)
  4289.     {
  4290.         bs->doAltAttack = 0;
  4291.     }
  4292.  
  4293.     if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
  4294.         !bs->doAltAttack)
  4295.     {
  4296.         bs->doAltAttack = 1;
  4297.     }
  4298.  
  4299.     return 0;
  4300. }
  4301.  
  4302. gentity_t *CheckForFriendInLOF(bot_state_t *bs)
  4303. {
  4304.     vec3_t fwd;
  4305.     vec3_t trfrom, trto;
  4306.     vec3_t mins, maxs;
  4307.     gentity_t *trent;
  4308.     trace_t tr;
  4309.  
  4310.     mins[0] = -3;
  4311.     mins[1] = -3;
  4312.     mins[2] = -3;
  4313.  
  4314.     maxs[0] = 3;
  4315.     maxs[1] = 3;
  4316.     maxs[2] = 3;
  4317.  
  4318.     AngleVectors(bs->viewangles, fwd, NULL, NULL);
  4319.  
  4320.     VectorCopy(bs->eye, trfrom);
  4321.  
  4322.     trto[0] = trfrom[0] + fwd[0]*2048;
  4323.     trto[1] = trfrom[1] + fwd[1]*2048;
  4324.     trto[2] = trfrom[2] + fwd[2]*2048;
  4325.  
  4326.     trap_Trace(&tr, trfrom, mins, maxs, trto, bs->client, MASK_PLAYERSOLID);
  4327.  
  4328.     if (tr.fraction != 1 && tr.entityNum <= MAX_CLIENTS)
  4329.     {
  4330.         trent = &g_entities[tr.entityNum];
  4331.  
  4332.         if (trent && trent->client)
  4333.         {
  4334.             if (IsTeamplay() && OnSameTeam(&g_entities[bs->client], trent))
  4335.             {
  4336.                 return trent;
  4337.             }
  4338.  
  4339.             if (botstates[trent->s.number] && GetLoveLevel(bs, botstates[trent->s.number]) > 1)
  4340.             {
  4341.                 return trent;
  4342.             }
  4343.         }
  4344.     }
  4345.  
  4346.     return NULL;
  4347. }
  4348.  
  4349. void BotScanForLeader(bot_state_t *bs)
  4350. { //bots will only automatically obtain a leader if it's another bot using this method.
  4351.     int i = 0;
  4352.     gentity_t *ent;
  4353.  
  4354.     if (bs->isSquadLeader)
  4355.     {
  4356.         return;
  4357.     }
  4358.  
  4359.     while (i < MAX_CLIENTS)
  4360.     {
  4361.         ent = &g_entities[i];
  4362.  
  4363.         if (ent && ent->client && botstates[i] && botstates[i]->isSquadLeader && bs->client != i)
  4364.         {
  4365.             if (OnSameTeam(&g_entities[bs->client], ent))
  4366.             {
  4367.                 bs->squadLeader = ent;
  4368.                 break;
  4369.             }
  4370.             if (GetLoveLevel(bs, botstates[i]) > 1 && !IsTeamplay())
  4371.             { //ignore love status regarding squad leaders if we're in teamplay
  4372.                 bs->squadLeader = ent;
  4373.                 break;
  4374.             }
  4375.         }
  4376.  
  4377.         i++;
  4378.     }
  4379. }
  4380.  
  4381. void BotReplyGreetings(bot_state_t *bs)
  4382. {
  4383.     int i = 0;
  4384.     int numhello = 0;
  4385.  
  4386.     while (i < MAX_CLIENTS)
  4387.     {
  4388.         if (botstates[i] &&
  4389.             botstates[i]->canChat &&
  4390.             i != bs->client)
  4391.         {
  4392.             botstates[i]->chatObject = &g_entities[bs->client];
  4393.             botstates[i]->chatAltObject = NULL;
  4394.             if (BotDoChat(botstates[i], "ResponseGreetings", 0))
  4395.             {
  4396.                 numhello++;
  4397.             }
  4398.         }
  4399.  
  4400.         if (numhello > 3)
  4401.         { //don't let more than 4 bots say hello at once
  4402.             return;
  4403.         }
  4404.  
  4405.         i++;
  4406.     }
  4407. }
  4408.  
  4409. void CTFFlagMovement(bot_state_t *bs)
  4410. {
  4411.     int diddrop = 0;
  4412.     gentity_t *desiredDrop = NULL;
  4413.     vec3_t a, mins, maxs;
  4414.     trace_t tr;
  4415.  
  4416.     mins[0] = -15;
  4417.     mins[1] = -15;
  4418.     mins[2] = -7;
  4419.     maxs[0] = 15;
  4420.     maxs[1] = 15;
  4421.     maxs[2] = 7;
  4422.  
  4423.     if (bs->wantFlag && (bs->wantFlag->flags & FL_DROPPED_ITEM))
  4424.     {
  4425.         if (bs->staticFlagSpot[0] == bs->wantFlag->s.pos.trBase[0] &&
  4426.             bs->staticFlagSpot[1] == bs->wantFlag->s.pos.trBase[1] &&
  4427.             bs->staticFlagSpot[2] == bs->wantFlag->s.pos.trBase[2])
  4428.         {
  4429.             VectorSubtract(bs->origin, bs->wantFlag->s.pos.trBase, a);
  4430.  
  4431.             if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE)
  4432.             {
  4433.                 VectorCopy(bs->wantFlag->s.pos.trBase, bs->goalPosition);
  4434.                 return;
  4435.             }
  4436.             else
  4437.             {
  4438.                 bs->wantFlag = NULL;
  4439.             }
  4440.         }
  4441.         else
  4442.         {
  4443.             bs->wantFlag = NULL;
  4444.         }
  4445.     }
  4446.     else if (bs->wantFlag)
  4447.     {
  4448.         bs->wantFlag = NULL;
  4449.     }
  4450.  
  4451.     if (flagRed && flagBlue)
  4452.     {
  4453.         if (bs->wpDestination == flagRed ||
  4454.             bs->wpDestination == flagBlue)
  4455.         {
  4456.             if (bs->wpDestination == flagRed && droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM) && droppedRedFlag->classname && strcmp(droppedRedFlag->classname, "freed") != 0)
  4457.             {
  4458.                 desiredDrop = droppedRedFlag;
  4459.                 diddrop = 1;
  4460.             }
  4461.             if (bs->wpDestination == flagBlue && droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM) && droppedBlueFlag->classname && strcmp(droppedBlueFlag->classname, "freed") != 0)
  4462.             {
  4463.                 desiredDrop = droppedBlueFlag;
  4464.                 diddrop = 1;
  4465.             }
  4466.  
  4467.             if (diddrop && desiredDrop)
  4468.             {
  4469.                 VectorSubtract(bs->origin, desiredDrop->s.pos.trBase, a);
  4470.  
  4471.                 if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE)
  4472.                 {
  4473.                     trap_Trace(&tr, bs->origin, mins, maxs, desiredDrop->s.pos.trBase, bs->client, MASK_SOLID);
  4474.  
  4475.                     if (tr.fraction == 1 || tr.entityNum == desiredDrop->s.number)
  4476.                     {
  4477.                         VectorCopy(desiredDrop->s.pos.trBase, bs->goalPosition);
  4478.                         VectorCopy(desiredDrop->s.pos.trBase, bs->staticFlagSpot);
  4479.                         return;
  4480.                     }
  4481.                 }
  4482.             }
  4483.         }
  4484.     }
  4485. }
  4486.  
  4487. int BotUseInventoryItem(bot_state_t *bs)
  4488. {
  4489. #ifdef BOT_USE_HOLDABLE
  4490.     if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC))
  4491.     {
  4492.         if (g_entities[bs->client].health <= 50)
  4493.         {
  4494.             bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC, IT_HOLDABLE);
  4495.             goto wantuseitem;
  4496.         }
  4497.     }
  4498.     if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER))
  4499.     {
  4500.         if (bs->currentEnemy && bs->frame_Enemy_Vis)
  4501.         {
  4502.             bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SEEKER, IT_HOLDABLE);
  4503.             goto wantuseitem;
  4504.         }
  4505.     }
  4506.     if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN))
  4507.     {
  4508.         if (bs->currentEnemy && bs->frame_Enemy_Vis)
  4509.         {
  4510.             bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SENTRY_GUN, IT_HOLDABLE);
  4511.             goto wantuseitem;
  4512.         }
  4513.     }
  4514.     if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD))
  4515.     {
  4516.         if (bs->currentEnemy && bs->frame_Enemy_Vis && bs->runningToEscapeThreat)
  4517.         { //this will (hopefully) result in the bot placing the shield down while facing
  4518.           //the enemy and running away
  4519.             bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SHIELD, IT_HOLDABLE);
  4520.             goto wantuseitem;
  4521.         }
  4522.     }
  4523.  
  4524.     return 0;
  4525.  
  4526. wantuseitem:
  4527.     level.clients[bs->client].ps.stats[STAT_HOLDABLE_ITEM] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM];
  4528.  
  4529.     return 1;
  4530. #else
  4531.     return 0;
  4532. #endif
  4533. }
  4534.  
  4535. int BotSurfaceNear(bot_state_t *bs)
  4536. {
  4537.     trace_t tr;
  4538.     vec3_t fwd;
  4539.  
  4540.     AngleVectors(bs->viewangles, fwd, NULL, NULL);
  4541.  
  4542.     fwd[0] = bs->origin[0]+(fwd[0]*64);
  4543.     fwd[1] = bs->origin[1]+(fwd[1]*64);
  4544.     fwd[2] = bs->origin[2]+(fwd[2]*64);
  4545.  
  4546.     trap_Trace(&tr, bs->origin, NULL, NULL, fwd, bs->client, MASK_SOLID);
  4547.  
  4548.     if (tr.fraction != 1)
  4549.     {
  4550.         return 1;
  4551.     }
  4552.  
  4553.     return 0;
  4554. }
  4555.  
  4556. wpobject_t *GetLastWP(bot_state_t *bs, int wpIndex)
  4557. {
  4558.     int goalWPIndex = 0;
  4559.  
  4560.     //reverse because we want the last wp to the current, not the next
  4561.     if (bs->wpDirection)
  4562.     {
  4563.         goalWPIndex = bs->wpCurrent->index+1;
  4564.     }
  4565.     else
  4566.     {
  4567.         goalWPIndex = bs->wpCurrent->index-1;
  4568.     }
  4569.  
  4570.     if (goalWPIndex < 0)
  4571.     {
  4572.         return NULL;
  4573.     }
  4574.  
  4575.     if (goalWPIndex >= gWPNum)
  4576.     {
  4577.         return NULL;
  4578.     }
  4579.  
  4580.     return gWPArray[goalWPIndex];
  4581. }
  4582.  
  4583. void StandardBotAI(bot_state_t *bs, float thinktime)
  4584. {
  4585.     int wp, enemy;
  4586.     int desiredIndex;
  4587.     int goalWPIndex;
  4588.     int doingFallback;
  4589.     vec3_t a, ang, headlevel, eorg, dif;
  4590.     float reaction;
  4591.     float bLeadAmount;
  4592.     int meleestrafe = 0;
  4593.     int cBAI = 0;
  4594.     gentity_t *friendInLOF = 0;
  4595.     int visResult = 0;
  4596.     int selResult = 0;
  4597.  
  4598.     trap_Cvar_Update(&bot_pause);
  4599.  
  4600.     if (gDeactivated || bot_pause.integer )
  4601.     {
  4602.         bs->wpCurrent = NULL;
  4603.         bs->currentEnemy = NULL;
  4604.         bs->wpDestination = NULL;
  4605.         bs->wpDirection = 0;
  4606.         return;
  4607.     }
  4608.  
  4609.     if (g_entities[bs->client].inuse &&
  4610.         g_entities[bs->client].client &&
  4611.         G_IsClientSpectating ( g_entities[bs->client].client ) )
  4612.     {
  4613.         bs->wpCurrent = NULL;
  4614.         bs->currentEnemy = NULL;
  4615.         bs->wpDestination = NULL;
  4616.         bs->wpDirection = 0;
  4617.         return;
  4618.     }
  4619.  
  4620.  
  4621.     if (!bs->lastDeadTime)
  4622.     { //just spawned in?
  4623.         bs->lastDeadTime = level.time;
  4624.     }
  4625.  
  4626.     if (g_entities[bs->client].health < 1)
  4627.     {
  4628.         bs->lastDeadTime = level.time;
  4629.  
  4630.         if (!bs->deathActivitiesDone && bs->lastHurt && bs->lastHurt->client && bs->lastHurt->s.number != bs->client)
  4631.         {
  4632.             BotDeathNotify(bs);
  4633.             if (PassLovedOneCheck(bs, bs->lastHurt))
  4634.             {
  4635.                 //CHAT: Died
  4636.                 bs->chatObject = bs->lastHurt;
  4637.                 bs->chatAltObject = NULL;
  4638.                 BotDoChat(bs, "Died", 0);
  4639.             }
  4640.             else if (!PassLovedOneCheck(bs, bs->lastHurt) &&
  4641.                 botstates[bs->lastHurt->s.number] &&
  4642.                 PassLovedOneCheck(botstates[bs->lastHurt->s.number], &g_entities[bs->client]))
  4643.             { //killed by a bot that I love, but that does not love me
  4644.                 bs->chatObject = bs->lastHurt;
  4645.                 bs->chatAltObject = NULL;
  4646.                 BotDoChat(bs, "KilledOnPurposeByLove", 0);
  4647.             }
  4648.  
  4649.             bs->deathActivitiesDone = 1;
  4650.         }
  4651.         
  4652.         bs->wpCurrent = NULL;
  4653.         bs->currentEnemy = NULL;
  4654.         bs->wpDestination = NULL;
  4655.         bs->wpCamping = NULL;
  4656.         bs->wpCampingTo = NULL;
  4657.         bs->wpStoreDest = NULL;
  4658.         bs->wpDestIgnoreTime = 0;
  4659.         bs->wpDestSwitchTime = 0;
  4660.         bs->wpSeenTime = 0;
  4661.         bs->wpDirection = 0;
  4662.  
  4663.         if (rand()%10 < 5 &&
  4664.             (!bs->doChat || bs->chatTime < level.time))
  4665.         {
  4666.             trap_EA_Attack(bs->client);
  4667.         }
  4668.  
  4669.         return;
  4670.     }
  4671.  
  4672.     bs->doAttack = 0;
  4673.     bs->doAltAttack = 0;
  4674.     //reset the attack states
  4675.  
  4676.     if (bs->isSquadLeader)
  4677.     {
  4678.         CommanderBotAI(bs);
  4679.     }
  4680.     else
  4681.     {
  4682.         BotDoTeamplayAI(bs);
  4683.     }
  4684.  
  4685.     if (!bs->currentEnemy)
  4686.     {
  4687.         bs->frame_Enemy_Vis = 0;
  4688.     }
  4689.  
  4690.     if (bs->revengeEnemy && bs->revengeEnemy->client &&
  4691.         bs->revengeEnemy->client->pers.connected != CA_ACTIVE && bs->revengeEnemy->client->pers.connected != CA_AUTHORIZING)
  4692.     {
  4693.         bs->revengeEnemy = NULL;
  4694.         bs->revengeHateLevel = 0;
  4695.     }
  4696.  
  4697.     if (bs->currentEnemy && bs->currentEnemy->client &&
  4698.         bs->currentEnemy->client->pers.connected != CA_ACTIVE && bs->currentEnemy->client->pers.connected != CA_AUTHORIZING)
  4699.     {
  4700.         bs->currentEnemy = NULL;
  4701.     }
  4702.  
  4703.     doingFallback = 0;
  4704.  
  4705.     bs->deathActivitiesDone = 0;
  4706.  
  4707.     if (BotUseInventoryItem(bs))
  4708.     {
  4709.         if (rand()%10 < 5)
  4710.         {
  4711.             trap_EA_Use(bs->client);
  4712.         }
  4713.     }
  4714.  
  4715.     if ((bs->cur_ps.ammo[weaponData[bs->cur_ps.weapon].attack[ATTACK_NORMAL].ammoIndex] < 1 && bs->cur_ps.clip[ATTACK_NORMAL][bs->cur_ps.weapon] < 1))
  4716.     {
  4717.         if (BotTryAnotherWeapon(bs))
  4718.         {
  4719.             return;
  4720.         }
  4721.     }
  4722.     else
  4723.     {
  4724.         if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number &&
  4725.             bs->frame_Enemy_Vis && bs->forceWeaponSelect /*&& bs->plantContinue < level.time*/)
  4726.         {
  4727.             bs->forceWeaponSelect = 0;
  4728.         }
  4729.  
  4730.         if (bs->forceWeaponSelect)
  4731.         {
  4732.             selResult = BotSelectChoiceWeapon(bs, bs->forceWeaponSelect, 1);
  4733.         }
  4734.  
  4735.         if (selResult)
  4736.         {
  4737.             if (selResult == 2)
  4738.             { //newly selected
  4739.                 return;
  4740.             }
  4741.         }
  4742.         else if (BotSelectIdealWeapon(bs))
  4743.         {
  4744.             return;
  4745.         }
  4746.     }
  4747.     /*if (BotSelectMelee(bs))
  4748.     {
  4749.         return;
  4750.     }*/
  4751.  
  4752.     reaction = bs->skills.reflex/bs->settings.skill;
  4753.  
  4754.     if (reaction < 0)
  4755.     {
  4756.         reaction = 0;
  4757.     }
  4758.     if (reaction > 2000)
  4759.     {
  4760.         reaction = 2000;
  4761.     }
  4762.  
  4763.     if (!bs->currentEnemy)
  4764.     {
  4765.         bs->timeToReact = level.time + reaction;
  4766.     }
  4767.  
  4768.     if (bs->wpCamping)
  4769.     {
  4770.         if (bs->isCamping < level.time)
  4771.         {
  4772.             bs->wpCamping = NULL;
  4773.             bs->isCamping = 0;
  4774.         }
  4775.  
  4776.         if (bs->currentEnemy && bs->frame_Enemy_Vis)
  4777.         {
  4778.             bs->wpCamping = NULL;
  4779.             bs->isCamping = 0;
  4780.         }
  4781.     }
  4782.  
  4783.     if (bs->wpCurrent &&
  4784.         (bs->wpSeenTime < level.time || bs->wpTravelTime < level.time))
  4785.     {
  4786.         bs->wpCurrent = NULL;
  4787.     }
  4788.  
  4789.     if (bs->currentEnemy)
  4790.     {
  4791.         if (bs->enemySeenTime < level.time ||
  4792.             !PassStandardEnemyChecks(bs, bs->currentEnemy))
  4793.         {
  4794.             if (bs->revengeEnemy == bs->currentEnemy &&
  4795.                 bs->currentEnemy->health < 1 &&
  4796.                 bs->lastAttacked && bs->lastAttacked == bs->currentEnemy)
  4797.             {
  4798.                 //CHAT: Destroyed hated one [KilledHatedOne section]
  4799.                 bs->chatObject = bs->revengeEnemy;
  4800.                 bs->chatAltObject = NULL;
  4801.                 BotDoChat(bs, "KilledHatedOne", 1);
  4802.                 bs->revengeEnemy = NULL;
  4803.                 bs->revengeHateLevel = 0;
  4804.             }
  4805.             else if (bs->currentEnemy->health < 1 && PassLovedOneCheck(bs, bs->currentEnemy) &&
  4806.                 bs->lastAttacked && bs->lastAttacked == bs->currentEnemy)
  4807.             {
  4808.                 //CHAT: Killed
  4809.                 bs->chatObject = bs->currentEnemy;
  4810.                 bs->chatAltObject = NULL;
  4811.                 BotDoChat(bs, "Killed", 0);
  4812.             }
  4813.  
  4814.             bs->currentEnemy = NULL;
  4815.         }
  4816.     }
  4817.  
  4818.     if (!bs->wpCurrent)
  4819.     {
  4820.         wp = GetNearestVisibleWP(bs->origin, bs->client);
  4821.  
  4822.         if (wp != -1)
  4823.         {
  4824.             bs->wpCurrent = gWPArray[wp];
  4825.             bs->wpSeenTime = level.time + 1500;
  4826.             bs->wpTravelTime = level.time + 10000; //never take more than 10 seconds to travel to a waypoint
  4827.         }
  4828.     }
  4829.  
  4830.     if (bs->enemySeenTime < level.time || !bs->frame_Enemy_Vis || !bs->currentEnemy ||
  4831.         (bs->currentEnemy /*&& bs->cur_ps.weapon == WP_SABER && bs->frame_Enemy_Len > 300*/))
  4832.     {
  4833.         enemy = ScanForEnemies(bs);
  4834.  
  4835.         if (enemy != -1)
  4836.         {
  4837.             bs->currentEnemy = &g_entities[enemy];
  4838.             bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
  4839.         }
  4840.     }
  4841.  
  4842.     if (!bs->squadLeader && !bs->isSquadLeader)
  4843.     {
  4844.         BotScanForLeader(bs);
  4845.     }
  4846.  
  4847.     if (!bs->squadLeader && bs->squadCannotLead < level.time)
  4848.     { //if still no leader after scanning, then become a squad leader
  4849.         bs->isSquadLeader = 1;
  4850.     }
  4851.  
  4852.     if (bs->isSquadLeader && bs->squadLeader)
  4853.     { //we don't follow anyone if we are a leader
  4854.         bs->squadLeader = NULL;
  4855.     }
  4856.  
  4857.     //ESTABLISH VISIBILITIES AND DISTANCES FOR THE WHOLE FRAME HERE
  4858.     if (bs->wpCurrent)
  4859.     {
  4860.         VectorSubtract(bs->wpCurrent->origin, bs->origin, a);
  4861.         bs->frame_Waypoint_Len = VectorLength(a);
  4862.  
  4863.         visResult = WPOrgVisible(&g_entities[bs->client], bs->origin, bs->wpCurrent->origin, bs->client);
  4864.  
  4865.         if (visResult == 2)
  4866.         {
  4867.             bs->frame_Waypoint_Vis = 0;
  4868.             bs->wpSeenTime = 0;
  4869.             bs->wpDestination = NULL;
  4870.             bs->wpDestIgnoreTime = level.time + 5000;
  4871.  
  4872.             if (bs->wpDirection)
  4873.             {
  4874.                 bs->wpDirection = 0;
  4875.             }
  4876.             else
  4877.             {
  4878.                 bs->wpDirection = 1;
  4879.             }
  4880.         }
  4881.         else if (visResult)
  4882.         {
  4883.             bs->frame_Waypoint_Vis = 1;
  4884.         }
  4885.         else
  4886.         {
  4887.             bs->frame_Waypoint_Vis = 0;
  4888.         }
  4889.     }
  4890.  
  4891.     if (bs->currentEnemy)
  4892.     {
  4893.         if (bs->currentEnemy->client)
  4894.         {
  4895.             VectorCopy(bs->currentEnemy->client->ps.origin, eorg);
  4896.             eorg[2] += bs->currentEnemy->client->ps.viewheight;
  4897.         }
  4898.         else
  4899.         {
  4900.             VectorCopy(bs->currentEnemy->s.origin, eorg);
  4901.         }
  4902.  
  4903.         VectorSubtract(eorg, bs->eye, a);
  4904.         bs->frame_Enemy_Len = VectorLength(a);
  4905.  
  4906.         if (OrgVisible(bs->eye, eorg, bs->client))
  4907.         {
  4908.             bs->frame_Enemy_Vis = 1;
  4909.             VectorCopy(eorg, bs->lastEnemySpotted);
  4910.             VectorCopy(bs->origin, bs->hereWhenSpotted);
  4911.             bs->lastVisibleEnemyIndex = bs->currentEnemy->s.number;
  4912.             //VectorCopy(bs->eye, bs->lastEnemySpotted);
  4913.             bs->hitSpotted = 0;
  4914.         }
  4915.         else
  4916.         {
  4917.             bs->frame_Enemy_Vis = 0;
  4918.         }
  4919.     }
  4920.     else
  4921.     {
  4922.         bs->lastVisibleEnemyIndex = ENTITYNUM_NONE;
  4923.     }
  4924.     //END
  4925.  
  4926.     if (bs->frame_Enemy_Vis)
  4927.     {
  4928.         bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
  4929.     }
  4930.  
  4931.     if (bs->wpCurrent)
  4932.     {
  4933.         WPConstantRoutine(bs);
  4934.  
  4935.         if (!bs->wpCurrent)
  4936.         { //WPConstantRoutine has the ability to nullify the waypoint if it fails certain checks, so..
  4937.             return;
  4938.         }
  4939.  
  4940.         if (bs->wpCurrent->flags & WPFLAG_WAITFORFUNC)
  4941.         {
  4942.             if (!CheckForFunc(bs->wpCurrent->origin, -1))
  4943.             {
  4944.                 bs->beStill = level.time + 500; //no func brush under.. wait
  4945.             }
  4946.         }
  4947.         if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC)
  4948.         {
  4949.             if (CheckForFunc(bs->wpCurrent->origin, -1))
  4950.             {
  4951.                 bs->beStill = level.time + 500; //func brush under.. wait
  4952.             }
  4953.         }
  4954.  
  4955.         if (bs->frame_Waypoint_Vis || (bs->wpCurrent->flags & WPFLAG_NOVIS))
  4956.         {
  4957.             bs->wpSeenTime = level.time + 1500; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it
  4958.         }
  4959.         VectorCopy(bs->wpCurrent->origin, bs->goalPosition);
  4960.         if (bs->wpDirection)
  4961.         {
  4962.             goalWPIndex = bs->wpCurrent->index-1;
  4963.         }
  4964.         else
  4965.         {
  4966.             goalWPIndex = bs->wpCurrent->index+1;
  4967.         }
  4968.  
  4969.         if (bs->wpCamping)
  4970.         {
  4971.             VectorSubtract(bs->wpCampingTo->origin, bs->origin, a);
  4972.             vectoangles(a, ang);
  4973.             VectorCopy(ang, bs->goalAngles);
  4974.  
  4975.             VectorSubtract(bs->origin, bs->wpCamping->origin, a);
  4976.             if (VectorLength(a) < 64)
  4977.             {
  4978.                 VectorCopy(bs->wpCamping->origin, bs->goalPosition);
  4979.                 bs->beStill = level.time + 1000;
  4980.  
  4981.                 if (!bs->campStanding)
  4982.                 {
  4983.                     bs->duckTime = level.time + 1000;
  4984.                 }
  4985.             }
  4986.         }
  4987.         /*
  4988.         else if (gWPArray[goalWPIndex] && gWPArray[goalWPIndex]->inuse &&
  4989.             !(bs->cur_ps.pm_flags & PMF_LADDER))
  4990.         { //don't look at one ahead if on a ladder..
  4991.             if (bs->cur_ps.pm_flags & PMF_LADDER)
  4992.             {
  4993.                 vec3_t ladderMod;
  4994.                 wpobject_t *prevWP;
  4995.  
  4996.                 prevWP = GetLastWP(bs, gWPArray[goalWPIndex]->index);
  4997.  
  4998.                 VectorCopy(gWPArray[goalWPIndex]->origin, ladderMod);
  4999.  
  5000.                 if (prevWP && prevWP->origin[2] < gWPArray[goalWPIndex]->origin[2])
  5001.                 { //We're going up a ladder, as opposed to down
  5002.                     ladderMod[2] += 400;
  5003.                 }
  5004.                 VectorSubtract(ladderMod, bs->origin, a);
  5005.                 vectoangles(a, ang);
  5006.                 VectorCopy(ang, bs->goalAngles);
  5007.  
  5008.                 //bs->jumpTime = level.time + 100;
  5009.             }
  5010.             else
  5011.             {
  5012.                 VectorSubtract(gWPArray[goalWPIndex]->origin, bs->origin, a);
  5013.                 vectoangles(a, ang);
  5014.                 VectorCopy(ang, bs->goalAngles);
  5015.             }
  5016.         }
  5017.         */
  5018.         //This code usually causes horrible issues in SoF2 because the levels are more cramped and tight.
  5019.         //So I guess for now they'll just have snappier viewangles.
  5020.         else
  5021.         {
  5022.             if (bs->cur_ps.pm_flags & PMF_LADDER)
  5023.             {
  5024.                 vec3_t ladderMod;
  5025.                 wpobject_t *prevWP;
  5026.  
  5027.                 prevWP = GetLastWP(bs, bs->wpCurrent->index);
  5028.  
  5029.                 VectorCopy(bs->wpCurrent->origin, ladderMod);
  5030.  
  5031.                 if (prevWP && prevWP->origin[2] < bs->wpCurrent->origin[2])
  5032.                 { //We're going up a ladder, as opposed to down
  5033.                     ladderMod[2] += 400;
  5034.                 }
  5035.                 VectorSubtract(ladderMod, bs->origin, a);
  5036.                 vectoangles(a, ang);
  5037.                 VectorCopy(ang, bs->goalAngles);
  5038.  
  5039.                 //bs->jumpTime = level.time + 100;
  5040.             }
  5041.             else
  5042.             {
  5043.                 VectorSubtract(bs->wpCurrent->origin, bs->origin, a);
  5044.                 vectoangles(a, ang);
  5045.                 VectorCopy(ang, bs->goalAngles);
  5046.             }
  5047.         }
  5048.  
  5049.         if (bs->destinationGrabTime < level.time /*&& (!bs->wpDestination || (bs->currentEnemy && bs->frame_Enemy_Vis))*/)
  5050.         {
  5051.             GetIdealDestination(bs);
  5052.         }
  5053.         
  5054.         if (bs->wpCurrent && bs->wpDestination)
  5055.         {
  5056.             if (TotalTrailDistance(bs->wpCurrent->index, bs->wpDestination->index, bs) == -1)
  5057.             {
  5058.                 bs->wpDestination = NULL;
  5059.                 bs->destinationGrabTime = level.time + 10000;
  5060.             }
  5061.         }
  5062.  
  5063.         if (bs->frame_Waypoint_Len < BOT_WPTOUCH_DISTANCE)
  5064.         {
  5065.             WPTouchRoutine(bs);
  5066.  
  5067.             if (!bs->wpDirection)
  5068.             {
  5069.                 desiredIndex = bs->wpCurrent->index+1;
  5070.             }
  5071.             else
  5072.             {
  5073.                 desiredIndex = bs->wpCurrent->index-1;
  5074.             }
  5075.  
  5076.             if (gWPArray[desiredIndex] &&
  5077.                 gWPArray[desiredIndex]->inuse &&
  5078.                 desiredIndex < gWPNum &&
  5079.                 desiredIndex >= 0 &&
  5080.                 PassWayCheck(bs, desiredIndex))
  5081.             {
  5082.                 bs->wpCurrent = gWPArray[desiredIndex];
  5083.             }
  5084.             else
  5085.             {
  5086.                 if (bs->wpDestination)
  5087.                 {
  5088.                     bs->wpDestination = NULL;
  5089.                     bs->destinationGrabTime = level.time + 10000;
  5090.                 }
  5091.  
  5092.                 if (bs->wpDirection)
  5093.                 {
  5094.                     bs->wpDirection = 0;
  5095.                 }
  5096.                 else
  5097.                 {
  5098.                     bs->wpDirection = 1;
  5099.                 }
  5100.             }
  5101.         }
  5102.     }
  5103.     else //We can't find a waypoint, going to need a fallback routine.
  5104.     {
  5105.         doingFallback = BotFallbackNavigation(bs);
  5106.     }
  5107.  
  5108.     if (bs->timeToReact < level.time && bs->currentEnemy && bs->enemySeenTime > level.time + (ENEMY_FORGET_MS - (ENEMY_FORGET_MS*0.2)))
  5109.     {
  5110.         if (bs->frame_Enemy_Vis)
  5111.         {
  5112.             cBAI = CombatBotAI(bs, thinktime);
  5113.         }
  5114.         else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
  5115.         { //keep charging in case we see him again before we lose track of him
  5116.             bs->doAltAttack = 1;
  5117.         }
  5118.  
  5119.         if (bs->destinationGrabTime > level.time + 100)
  5120.         {
  5121.             bs->destinationGrabTime = level.time + 100; //assures that we will continue staying within a general area of where we want to be in a combat situation
  5122.         }
  5123.  
  5124.         if (bs->cur_ps.pm_flags & PMF_LADDER)
  5125.         { //keep navigating the ladder but if anyone happens in front of us on it, shoot them up good.
  5126.             goto skipviewchecks;
  5127.         }
  5128.  
  5129.         if (bs->currentEnemy->client)
  5130.         {
  5131.             VectorCopy(bs->currentEnemy->client->ps.origin, headlevel);
  5132.             headlevel[2] += bs->currentEnemy->client->ps.viewheight;
  5133.         }
  5134.         else
  5135.         {
  5136.             VectorCopy(bs->currentEnemy->client->ps.origin, headlevel);
  5137.         }
  5138.  
  5139.         bLeadAmount = BotWeaponCanLead(bs);
  5140.         if ((bs->skills.accuracy/bs->settings.skill) <= 8 &&
  5141.             bLeadAmount)
  5142.         {
  5143.             BotAimLeading(bs, headlevel, bLeadAmount);
  5144.         }
  5145.         else
  5146.         {
  5147.             VectorSubtract(headlevel, bs->eye, a);
  5148.             vectoangles(a, ang);
  5149.             VectorCopy(ang, bs->goalAngles);
  5150.         }
  5151.  
  5152. #ifdef BOT_LAUNCH_ANGLES
  5153.         if ((CAT_GRENADE == weapon->category ||
  5154.              WP_MM1_GRENADE_LAUNCHER == bs->cur_ps.weapon) && bs->launchAngle < 90)
  5155.         {
  5156.             bs->goalAngles[PITCH] = bs->goalAngles[PITCH]-bs->launchAngle;
  5157.         }
  5158. #endif
  5159.  
  5160.         BotAimOffsetGoalAngles(bs);
  5161.     }
  5162. skipviewchecks:
  5163.     if (bs->currentEnemy)
  5164.     {
  5165.         if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)
  5166.         {
  5167.             if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE)
  5168.             {
  5169.                 MeleeCombatHandling(bs);
  5170.                 meleestrafe = 1;
  5171.             }
  5172.         }
  5173.     }
  5174.  
  5175.     if (doingFallback && bs->currentEnemy) //just stand and fire if we have no idea where we are
  5176.     {
  5177.         VectorCopy(bs->origin, bs->goalPosition);
  5178.     }
  5179.  
  5180.     if (bs->doChat && bs->chatTime > level.time && (!bs->currentEnemy || !bs->frame_Enemy_Vis))
  5181.     {
  5182.         return;
  5183.     }
  5184.     else if (bs->doChat && bs->currentEnemy && bs->frame_Enemy_Vis)
  5185.     {
  5186.         //bs->chatTime = level.time + bs->chatTime_stored;
  5187.         bs->doChat = 0; //do we want to keep the bot waiting to chat until after the enemy is gone?
  5188.         bs->chatTeam = 0;
  5189.     }
  5190.     else if (bs->doChat && bs->chatTime <= level.time)
  5191.     {
  5192.         if (bs->chatTeam)
  5193.         {
  5194.             trap_EA_SayTeam(bs->client, bs->currentChat);
  5195.             bs->chatTeam = 0;
  5196.         }
  5197.         else
  5198.         {
  5199.             trap_EA_Say(bs->client, bs->currentChat);
  5200.         }
  5201.         if (bs->doChat == 2)
  5202.         {
  5203.             BotReplyGreetings(bs);
  5204.         }
  5205.         bs->doChat = 0;
  5206.     }
  5207.  
  5208.     CTFFlagMovement(bs);
  5209.  
  5210.     if (/*bs->wpDestination &&*/ bs->shootGoal &&
  5211.         /*bs->wpDestination->associated_entity == bs->shootGoal->s.number &&*/
  5212.         bs->shootGoal->health > 0 && bs->shootGoal->takedamage)
  5213.     {
  5214.         dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
  5215.         dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
  5216.         dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
  5217.  
  5218.         if (!bs->currentEnemy || bs->frame_Enemy_Len > 256)
  5219.         { //if someone is close then don't stop shooting them for this
  5220.             VectorSubtract(dif, bs->eye, a);
  5221.             vectoangles(a, a);
  5222.             VectorCopy(a, bs->goalAngles);
  5223.  
  5224.             if (InFieldOfVision(bs->viewangles, 30, a) &&
  5225.                 EntityVisibleBox(bs->origin, NULL, NULL, dif, bs->client, bs->shootGoal->s.number))
  5226.             {
  5227.                 bs->doAttack = 1;
  5228.             }
  5229.         }
  5230.     }
  5231.  
  5232.     if (bs->beStill < level.time && !WaitingForNow(bs, bs->goalPosition))
  5233.     {
  5234.         VectorSubtract(bs->goalPosition, bs->origin, bs->goalMovedir);
  5235.         VectorNormalize(bs->goalMovedir);
  5236.  
  5237.         if (bs->jumpTime > level.time && bs->jDelay < level.time &&
  5238.             level.clients[bs->client].pers.cmd.upmove > 0)
  5239.         {
  5240.         //    trap_EA_Move(bs->client, bs->origin, 5000);
  5241.             bs->beStill = level.time + 200;
  5242.         }
  5243.         else
  5244.         {
  5245.             trap_EA_Move(bs->client, bs->goalMovedir, 5000);
  5246.         }
  5247.  
  5248.         if (meleestrafe)
  5249.         {
  5250.             StrafeTracing(bs);
  5251.         }
  5252.  
  5253.         if (bs->meleeStrafeDir && meleestrafe && bs->meleeStrafeDisable < level.time)
  5254.         {
  5255.             trap_EA_MoveRight(bs->client);
  5256.         }
  5257.         else if (meleestrafe && bs->meleeStrafeDisable < level.time)
  5258.         {
  5259.             trap_EA_MoveLeft(bs->client);
  5260.         }
  5261.  
  5262.         if (BotTrace_Jump(bs, bs->goalPosition))
  5263.         {
  5264.             bs->jumpTime = level.time + 100;
  5265.         }
  5266.         else if (BotTrace_Duck(bs, bs->goalPosition))
  5267.         {
  5268.             bs->duckTime = level.time + 100;
  5269.         }
  5270. #ifdef BOT_STRAFE_AVOIDANCE
  5271.         else
  5272.         {
  5273.             int strafeAround = BotTrace_Strafe(bs, bs->goalPosition);
  5274.  
  5275.             if (strafeAround == STRAFEAROUND_RIGHT)
  5276.             {
  5277.                 trap_EA_MoveRight(bs->client);
  5278.             }
  5279.             else if (strafeAround == STRAFEAROUND_LEFT)
  5280.             {
  5281.                 trap_EA_MoveLeft(bs->client);
  5282.             }
  5283.         }
  5284. #endif
  5285.     }
  5286.  
  5287.     if (bs->jumpTime > level.time && bs->jDelay < level.time)
  5288.     {
  5289.         if (!(bs->cur_ps.pm_debounce & PMD_JUMP ))
  5290.         {
  5291.             trap_EA_Jump(bs->client);
  5292.         }
  5293.     }
  5294.  
  5295.     if (bs->duckTime > level.time)
  5296.     {
  5297.         trap_EA_Crouch(bs->client);
  5298.     }
  5299.  
  5300.     if ( bs->dangerousObject && bs->dangerousObject->inuse && bs->dangerousObject->health > 0 &&
  5301.         bs->dangerousObject->takedamage && (!bs->frame_Enemy_Vis || !bs->currentEnemy) &&
  5302.         (BotGetWeaponRange(bs) == BWEAPONRANGE_MID || BotGetWeaponRange(bs) == BWEAPONRANGE_LONG) &&
  5303. //        bs->cur_ps.weapon != WP_DET_PACK && bs->cur_ps.weapon != WP_TRIP_MINE &&
  5304.         !bs->shootGoal )
  5305.     {
  5306.         float danLen;
  5307.  
  5308.         VectorSubtract(bs->dangerousObject->r.currentOrigin, bs->eye, a);
  5309.  
  5310.         danLen = VectorLength(a);
  5311.  
  5312.         if (danLen > 256)
  5313.         {
  5314.             vectoangles(a, a);
  5315.             VectorCopy(a, bs->goalAngles);
  5316.  
  5317.             if (Q_irand(1, 10) < 5)
  5318.             {
  5319.                 bs->goalAngles[YAW] += Q_irand(0, 3);
  5320.                 bs->goalAngles[PITCH] += Q_irand(0, 3);
  5321.             }
  5322.             else
  5323.             {
  5324.                 bs->goalAngles[YAW] -= Q_irand(0, 3);
  5325.                 bs->goalAngles[PITCH] -= Q_irand(0, 3);
  5326.             }
  5327.  
  5328.             if (InFieldOfVision(bs->viewangles, 30, a) &&
  5329.                 EntityVisibleBox(bs->origin, NULL, NULL, bs->dangerousObject->r.currentOrigin, bs->client, bs->dangerousObject->s.number))
  5330.             {
  5331.                 bs->doAttack = 1;
  5332.             }            
  5333.         }
  5334.     }
  5335.  
  5336.     if (PrimFiring(bs) ||
  5337.         AltFiring(bs))
  5338.     {
  5339.         friendInLOF = CheckForFriendInLOF(bs);
  5340.  
  5341.         if (friendInLOF)
  5342.         {
  5343.             if (PrimFiring(bs))
  5344.             {
  5345.                 KeepPrimFromFiring(bs);
  5346.             }
  5347.             if (AltFiring(bs))
  5348.             {
  5349.                 KeepAltFromFiring(bs);
  5350.             }
  5351.         }
  5352.     }
  5353.  
  5354.     if (bs->doAttack)
  5355.     {
  5356.         trap_EA_Attack(bs->client);
  5357.     }
  5358.     else if (bs->doAltAttack)
  5359.     {
  5360.         trap_EA_Alt_Attack(bs->client);
  5361.     }
  5362.  
  5363.     MoveTowardIdealAngles(bs);
  5364. }
  5365. //end rww
  5366.  
  5367. /*
  5368. ==================
  5369. BotAIStartFrame
  5370. ==================
  5371. */
  5372. int BotAIStartFrame(int time) 
  5373. {
  5374. #ifdef _SOF2_BOTS
  5375.     int i;
  5376.     int elapsed_time, thinktime;
  5377.     static int local_time;
  5378.     static int botlib_residual;
  5379.     static int lastbotthink_time;
  5380.  
  5381.     G_CheckBotSpawn();
  5382.  
  5383.     //rww - addl bot frame functions
  5384.     if (gBotEdit)
  5385.     {
  5386.         trap_Cvar_Update(&bot_wp_info);
  5387.         BotWaypointRender();
  5388.     }
  5389.  
  5390.     UpdateEventTracker();
  5391.     //end rww
  5392.  
  5393.     //cap the bot think time
  5394.     //if the bot think time changed we should reschedule the bots
  5395.     if (BOT_THINK_TIME != lastbotthink_time) {
  5396.         lastbotthink_time = BOT_THINK_TIME;
  5397.         BotScheduleBotThink();
  5398.     }
  5399.  
  5400.     elapsed_time = time - local_time;
  5401.     local_time = time;
  5402.  
  5403.     if (elapsed_time > BOT_THINK_TIME) thinktime = elapsed_time;
  5404.     else thinktime = BOT_THINK_TIME;
  5405.  
  5406.     // execute scheduled bot AI
  5407.     for( i = 0; i < MAX_CLIENTS; i++ ) {
  5408.         if( !botstates[i] || !botstates[i]->inuse ) {
  5409.             continue;
  5410.         }
  5411.         //
  5412.         botstates[i]->botthink_residual += elapsed_time;
  5413.         //
  5414.         if ( botstates[i]->botthink_residual >= thinktime ) {
  5415.             botstates[i]->botthink_residual -= thinktime;
  5416.  
  5417.             if (g_entities[i].client->pers.connected == CON_CONNECTED) {
  5418.                 BotAI(i, (float) thinktime / 1000);
  5419.             }
  5420.         }
  5421.     }
  5422.  
  5423.     // execute bot user commands every frame
  5424.     for( i = 0; i < MAX_CLIENTS; i++ ) {
  5425.         if( !botstates[i] || !botstates[i]->inuse ) {
  5426.             continue;
  5427.         }
  5428.         if( g_entities[i].client->pers.connected != CON_CONNECTED ) {
  5429.             continue;
  5430.         }
  5431.  
  5432.         BotUpdateInput(botstates[i], time, elapsed_time);
  5433.         trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd);
  5434.     }
  5435.  
  5436.     return qtrue;
  5437. #else
  5438.     return qfalse;
  5439. #endif
  5440. }
  5441.  
  5442. /*
  5443. ==============
  5444. BotAISetup
  5445. ==============
  5446. */
  5447. int BotAISetup( int restart ) {
  5448.     //rww - new bot cvars..
  5449. #ifdef _DEBUG
  5450.     trap_Cvar_Register(&bot_debugmessages, "bot_debugmessages", "0", CVAR_CHEAT, 0.0, 0.0);
  5451. #endif
  5452.  
  5453.     trap_Cvar_Register(&bot_attachments, "bot_attachments", "1", 0, 0.0, 0.0);
  5454.     trap_Cvar_Register(&bot_camp, "bot_camp", "1", 0, 0.0, 0.0);
  5455.     trap_Cvar_Register(&bot_pause, "bot_pause", "0", 0, 0.0, 0.0);
  5456.  
  5457.     trap_Cvar_Register(&bot_wp_info, "bot_wp_info", "1", 0, 0.0, 0.0);
  5458.     trap_Cvar_Register(&bot_wp_edit, "bot_wp_edit", "0", CVAR_CHEAT, 0.0, 0.0);
  5459.     trap_Cvar_Register(&bot_wp_clearweight, "bot_wp_clearweight", "1", 0, 0.0, 0.0);
  5460.     trap_Cvar_Register(&bot_wp_distconnect, "bot_wp_distconnect", "1", 0, 0.0, 0.0);
  5461.     trap_Cvar_Register(&bot_wp_visconnect, "bot_wp_visconnect", "1", 0, 0.0, 0.0);
  5462.  
  5463.     //end rww
  5464.  
  5465.     //if the game is restarted for a tournament
  5466.     if (restart) {
  5467.         return qtrue;
  5468.     }
  5469.  
  5470.     //initialize the bot states
  5471.     memset( botstates, 0, sizeof(botstates) );
  5472.  
  5473.     if (!trap_BotLibSetup())
  5474.     {
  5475.         return qfalse; //wts?!
  5476.     }
  5477.  
  5478.     return qtrue;
  5479. }
  5480.  
  5481. /*
  5482. ==============
  5483. BotAIShutdown
  5484. ==============
  5485. */
  5486. int BotAIShutdown( int restart ) {
  5487.  
  5488.     int i;
  5489.  
  5490.     //if the game is restarted for a tournament
  5491.     if ( restart ) {
  5492.         //shutdown all the bots in the botlib
  5493.         for (i = 0; i < MAX_CLIENTS; i++) {
  5494.             if (botstates[i] && botstates[i]->inuse) {
  5495.                 BotAIShutdownClient(botstates[i]->client, restart);
  5496.             }
  5497.         }
  5498.         //don't shutdown the bot library
  5499.     }
  5500.     else {
  5501.         trap_BotLibShutdown();
  5502.     }
  5503.     return qtrue;
  5504. }
  5505.  
  5506.